ข้ามไปยังเนื้อหา

$effect — Side Effects

useEffect มี footgun ที่เป็นที่รู้จักดี: ลืมตัวแปรใน dependency array และ effect ของคุณจะรันด้วยค่าเก่า ใส่มากเกินไปและจะรันบ่อยเกินไป $effect ของ Svelte 5 แก้ปัญหานี้โดยสิ้นเชิง — ไม่มี dependency array effect รันครั้งแรก จากนั้นรันใหม่อัตโนมัติทุกครั้งที่ reactive value ที่อ่านในการรันก่อนหน้าเปลี่ยน คอมไพเลอร์ track dependencies แทนคุณ

React
function TitleSync() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
document.title = 'Count: ' + count;
}, [count]); // <-- must list count here
return <button onClick={() => setCount(c => c + 1)}>+1 (count: {count})</button>;
}
Svelte
<script>
let count = $state(0);
$effect(() => {
document.title = 'Count: ' + count;
// count ถูกอ่านที่นี่ จึงถูก track อัตโนมัติ
});
</script>
<button onclick={() => count++}>+1 (count: {count})</button>

ไม่มี [count] effect อ่าน count ดังนั้น Svelte รู้ว่าต้องรันใหม่เมื่อ count เปลี่ยน

  • การรันแรก: หลัง component mount (เหมือน useEffect ที่ไม่มี deps)
  • รันใหม่: synchronously หลัง reactive dependency ที่อ่านในการรันก่อนหน้าเปลี่ยน
  • ไม่รันระหว่าง render: ต่างจาก useMemo, $effect ไม่รันในขั้นตอน rendering แบบ synchronous

Return function จาก $effect เพื่อ cleanup ก่อนการรันครั้งถัดไปหรือเมื่อ component ถูกทำลาย — เหมือนรูปแบบ cleanup ของ useEffect ทุกประการ

React
function Timer() {
const [seconds, setSeconds] = React.useState(0);
React.useEffect(() => {
const id = setInterval(() => setSeconds(s => s + 1), 1000);
return () => clearInterval(id); // cleanup
}, []);
return <p>Elapsed: {seconds}s</p>;
}
Svelte
<script>
import { onMount } from 'svelte';
let seconds = $state(0);
onMount(() => {
const id = setInterval(() => seconds++, 1000);
return () => clearInterval(id); // cleanup
});
</script>
<p>Elapsed: {seconds}s</p>

สำหรับรูปแบบ “รันครั้งเดียวเมื่อ mount + cleanup เมื่อ destroy”, onMount ใช้งานง่ายกว่า $effect ดูเพิ่มเติมในบทเรียน Lifecycle

กรณีการใช้งาน useEffect ที่พบบ่อย — sync state กับ localStorage — map ตรงกัน:

React
function ThemeToggle() {
const [theme, setTheme] = React.useState(
() => localStorage.getItem('theme') ?? 'light'
);
React.useEffect(() => {
localStorage.setItem('theme', theme);
document.body.setAttribute('data-theme', theme);
}, [theme]);
return (
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
Theme: {theme}
</button>
);
}
Svelte
<script>
let theme = $state(
typeof localStorage !== 'undefined'
? (localStorage.getItem('theme') ?? 'light')
: 'light'
);
$effect(() => {
localStorage.setItem('theme', theme);
document.body.setAttribute('data-theme', theme);
});
</script>
<button onclick={() => theme = theme === 'light' ? 'dark' : 'light'}>
Theme: {theme}
</button>
$effect รู้ได้อย่างไรว่าจะ re-run บน reactive values ไหน?
จะ cleanup $effect ยังไง (เช่น clear interval)?
ต้องการให้ effect รันครั้งเดียวหลัง mount วิธีการ idiomatic ของ Svelte คืออะไร?