شرح Svelte 5 Runes: $state, $derived, $effect (2026)
١٩ يونيو ٢٠٢٦
تُعد الـ Runes هي العناصر الأساسية الصريحة للتفاعلية في Svelte 5، وتُكتب ببادئة $. استخدم $state للإعلان عن القيم التفاعلية، و $derived لحساب القيم بناءً على حالة أخرى، و $effect فقط للتأثيرات الجانبية (side effects) مثل طلبات الشبكة أو التعامل مع DOM. الجأ إلى $derived قبل $effect.
ملخص
استبدل Svelte 5 (المستقر منذ 19 أكتوبر 2024؛ والإصدار 5.56.x اعتباراً من يونيو 2026) التفاعلية الضمنية للمترجم بـ runes — وهي رموز تشبه الدوال مثل $state و $derived و $effect.12 التحول الفكري بسيط: $state يحفظ القيم، و $derived يحسب القيم، و $effect ينفذ التأثيرات الجانبية. الخطأ الشائع هو استخدام $effect لمزامنة قيمة مع أخرى — تنصح التوثيقات صراحةً بعدم القيام بذلك؛ استخدم $derived بدلاً منه.3 لا تزال مخازن (stores) Svelte 4 تعمل، لذا يمكن أن يكون الانتقال تدريجياً.4
ما ستتعلمه
- ما هي الـ runes ولماذا قدمها Svelte 5
- كيف يعمل
$state، بما في ذلك التفاعلية العميقة، و$state.raw، و$state.snapshot - كيف يحل
$derivedو$derived.byمحل القيم المحسوبة بـ$: - ما هو الغرض من
$effect— والحالات التي يجب ألا تستخدمه فيها - قاعدة اتخاذ القرار للمفاضلة بين
$derivedو$effect - كيف يحل
$propsو$bindableمحلexport let - هل لا تزال مخازن Svelte تعمل، وكيفية نقل تفاعلية Svelte 4 إلى الـ runes
ما هي الـ runes في Svelte 5؟
الـ Runes هي رموز خاصة، تبدأ بـ $، تخبر مترجم Svelte بكيفية عمل التفاعلية. تبدو مثل استدعاءات الدوال ولكنها كلمات مفتاحية للمترجم — لا تقوم باستيرادها، ولها معنى فقط داخل ملفات .svelte و .svelte.js و .svelte.ts.4 يوفر Svelte 5 سبع runes: $state، و $derived، و $effect، و $props، و $bindable، و $inspect، و $host.5
قبل الـ runes، كان Svelte 4 يجعل كل let في المستوى الأعلى للمكون تفاعلياً ويستخدم علامة $: للقيم المحسوبة. كان ذلك موجزاً ولكنه "سحري": التفاعلية كانت تعتمد على مكان وجود الكود. تجعل الـ Runes التفاعلية صريحة وقابلة للنقل — فنفس الـ $state يعمل داخل مكون أو داخل وحدة .svelte.js عادية — وهذا هو السبب في أنه يمكنك الآن الاحتفاظ بالمنطق التفاعلي المشترك خارج المكونات تماماً.4
<script>
let count = $state(0);
let doubled = $derived(count * 2);
</script>
<button onclick={() => count++}>
clicked {count} times — doubled is {doubled}
</button>
كيف يعمل $state في Svelte 5؟
يعلن $state عن قيمة تفاعلية: عندما تتغير، يتم تحديث واجهة المستخدم التي تقرأها. إنه البديل المباشر لـ let التفاعلي العادي من Svelte 4.5
<script>
let count = $state(0);
let user = $state({ name: 'Ada', tags: ['admin'] });
</script>
<button onclick={() => count++}>{count}</button>
<button onclick={() => user.tags.push('editor')}>add tag</button>
التفصيل المهم: الكائنات والمصفوفات التي يتم تمريرها إلى $state تصبح proxies تفاعلية بعمق. تعديل خاصية متداخلة — مثل user.tags.push(...) أعلاه — يؤدي إلى إطلاق التحديثات، دون الحاجة إلى إعادة التعيين.6 هذا تغيير حقيقي عن Svelte 4، حيث كان عليك إعادة التعيين (user = user) لفرض التحديث.
هناك نوعان مختلفان من $state للتعامل مع الحالات الخاصة:
$state.rawينشئ حالة سطحية (shallow). لا يتم جعلها تفاعلية بعمق وتتحدث فقط عندما تقوم بـ استبدالها (إعادة تعيين القيمة بالكامل)، وليس عند تعديل أجزائها. هذا يتجنب تكلفة الـ proxying للكائنات الكبيرة أو المصفوفات التي لا تقوم بتعديلها أبداً.7$state.snapshotيرجع نسخة منفصلة وعادية (ليست proxy) من كائن$stateعميق. تعديل اللقطة (snapshot) لا يؤثر على الأصل، وقراءتها لا تسجل تبعية — وهو أمر مفيد عند تمرير البيانات إلى مكتبة غير تابعة لـ Svelte لا تقبل الـ proxies.7
يمكنك أيضاً تمييز حقول الفئات (class fields) كـ $state، وهي الطريقة التي تبني بها فئات تفاعلية قابلة لإعادة الاستخدام.6
كيف يعمل $derived؟ ($derived مقابل $:)
يعلن $derived عن قيمة محسوبة من حالة تفاعلية أخرى، ويتم إعادة حسابها تلقائياً عندما تتغير تبعياتها. إنه يحل محل إعلانات $: المحسوبة في Svelte 4.6
<script>
let count = $state(0);
let doubled = $derived(count * 2);
</script>
التعبير داخل $derived(...) يجب أن يكون خالياً من التأثيرات الجانبية — يمنع Svelte تغييرات الحالة مثل count++ داخل الـ derived.6 للمنطق الذي لا يتناسب مع تعبير واحد، استخدم $derived.by، الذي يأخذ دالة:
<script>
let numbers = $state([1, 2, 3]);
let total = $derived.by(() => {
let sum = 0;
for (const n of numbers) sum += n;
return sum;
});
</script>
$derived(expr) يعادل تماماً $derived.by(() => expr).6 هناك ثلاثة سلوكيات تستحق المعرفة لأنها تفسر لماذا الـ deriveds غير مكلفة:
- كسولة ومخزنة (Lazy and memoized). يستخدم Svelte تفاعلية الدفع والسحب (push-pull): عندما تتغير الحالة، يتم إخطار التابعين (دفع)، ولكن لا يتم إعادة حساب الـ derived حتى يتم قراءته فعلياً (سحب). الـ derived الذي لا يتم الوصول إليه أبداً لا يعمل أبداً.6
- الاختصار المرجعي (Referential short-circuiting). إذا كان الـ derived المعاد حسابه مطابقاً مرجعياً لقيمته السابقة، يتم تخطي التحديثات اللاحقة.6
- قابل للتجاوز (Overridable). منذ Svelte 5.25، يمكنك إعادة تعيين الـ derived مؤقتاً (ما لم يكن
const) — وهو أمر مفيد لواجهات المستخدم المتفائلة (optimistic UI) حيث تعرض تغييراً فورياً قبل تأكيد الخادم له.6
متى يجب استخدام $effect — ومتى لا يجب؟
يقوم $effect بتشغيل دالة عندما تتغير تبعياتها التفاعلية. إنه مخصص لـ التأثيرات الجانبية: استدعاء مكتبات خارجية، الرسم على <canvas>، إجراء طلبات الشبكة، أو التحليلات. تعمل التأثيرات في المتصفح فقط — ولا تعمل أبداً أثناء الرندر من جهة الخادم (SSR).3
<script>
let theme = $state('dark');
$effect(() => {
document.documentElement.dataset.theme = theme;
});
</script>
السلوكيات الرئيسية من التوثيق:3
- تعمل الـ Effects بعد تركيب المكون (mount) في الـ DOM، ثم في microtask بعد تغييرات الحالة. يتم تجميع عمليات إعادة التشغيل (Re-runs) وتنفيذها بعد تطبيق تحديثات الـ DOM.
- يتتبع الـ effect القيم التفاعلية (
$state،$derived،$props) التي تُقرأ بشكل متزامن في جسمه. القيم التي تُقرأ بشكل غير متزامن — بعدawaitأو داخلsetTimeout— لا يتم تتبعها. - يمكن للـ effect إرجاع وظيفة تفكيك (teardown)، والتي تعمل مباشرة قبل إعادة تشغيل الـ effect وعند تدمير المكون (مثالية لـ
clearInterval):
<script>
let ms = $state(1000);
let count = $state(0);
$effect(() => {
const id = setInterval(() => count++, ms);
return () => clearInterval(id); // teardown
});
</script>
الآن الجزء الذي يستحق التأكيد. التوجيه الرسمي هو أن $effect هو مخرج طوارئ (escape hatch)، وليس أداة افتراضية. وبشكل خاص، لا تستخدمه لمزامنة الحالة.3 هذا هو النمط الخاطئ (anti-pattern):
<script>
let count = $state(0);
let doubled = $state();
// ❌ لا تفعل هذا
$effect(() => {
doubled = count * 2;
});
</script>
استبدله بـ derived، وهو أبسط، وأكثر كفاءة (lazy)، ويتجنب دورات الرندر الإضافية:
<script>
let count = $state(0);
let doubled = $derived(count * 2); // ✅ افعل هذا
</script>
إذا اضطررت يوماً ما للكتابة في $state داخل effect وواجهت حلقة لا نهائية لأنك تقرأ وتكتب نفس الحالة، فقم بلف القراءة في untrack (المستوردة من svelte) حتى لا يتم تسجيلها كاعتمادية (dependency).3 لربط مدخلين معاً، فضل روابط الدوال (function bindings) أو استدعاءات oninput على الـ effects.3
توفر Svelte أيضاً runes فرعية متقدمة: $effect.pre (يعمل قبل تحديث الـ DOM، مثل التمرير التلقائي عبر tick())، و $effect.tracking() (لمعرفة ما إذا كنت في سياق تتبع)، و $effect.pending() (عدد الـ promises المعلقة عند استخدام await في المكونات — جزء من الميزة التجريبية للـ async التي تختار تفعيلها عبر experimental.async)، و $effect.root() (نطاق effect يتم إدارته يدوياً ولا ينظف نفسه تلقائياً).3
$derived مقابل $effect: أي rune يجب أن تستخدم؟
استخدم قاعدة القرار هذه: إذا كنت تنتج قيمة، استخدم $derived؛ وإذا كنت تقوم بإجراء (action)، استخدم $effect. الجدول أدناه يوضح المهام الشائعة.
| تريد أن… | استخدم | لماذا |
|---|---|---|
| تحسب قيمة من حالة (state) | $derived | نقية، كفؤة (lazy)، ومخزنة (memoized)؛ يتم إعادة حسابها فقط عند القراءة |
| حساب متعدد الجمل | $derived.by | نفس $derived ولكن مع جسم دالة |
| عكس قيمة حالة في حالة أخرى | $derived | أبداً لا تستخدم $effect — التوثيق يشير إلى هذا كنمط خاطئ3 |
| استدعاء API / تسجيل (log) / تعديل الـ DOM | $effect | تأثير جانبي (side effect) حقيقي، للمتصفح فقط |
| إعداد وإنهاء اشتراك/مؤقت | $effect + teardown | إرجاع دالة تنظيف (cleanup) |
| تشغيل كود قبل تحديث الـ DOM | $effect.pre | يعمل قبل تحديثات الـ DOM |
اختبار بسيط: إذا كان جسم الـ effect الخاص بك ينتهي بتعيين قيمة لـ $state آخر، فأنت بالتأكيد تريد $derived بدلاً من ذلك.
كيف تعمل $props و $bindable؟
يقرأ $props مدخلات المكون عبر التفكيك (destructuring)، مع قيم افتراضية، ليحل محل export let في Svelte 4:8
<!-- Greeting.svelte -->
<script>
let { name = 'world', count = 0 } = $props();
</script>
<p>Hello {name} ({count})</p>
<!-- TextInput.svelte -->
<script>
let { value = $bindable('') } = $props();
</script>
<input bind:value />
<!-- Parent.svelte -->
<script>
import TextInput from './TextInput.svelte';
let name = $state('');
</script>
<TextInput bind:value={name} />
<p>{name}</p>
هل لا تزال Svelte stores تعمل في Svelte 5؟
نعم. الـ stores من svelte/store — مثل writable و readable و derived، وصيغة الاشتراك التلقائي $store — لا تزال تعمل في Svelte 5 ولم يتم إيقافها.4 التوصية الرسمية هي تفضيل الـ runes لمعظم المكونات والحالات المشتركة، لكن الـ stores تظل مفيدة، خاصة للأكواد الموجودة مسبقاً ولحالات مثل الاشتراكات الخارجية بأسلوب RxJS. ولأن كلاهما يتعايشان معاً، فلن تحتاج أبداً إلى إعادة كتابة شاملة ومفاجئة.
النمط الشائع للحالة التفاعلية المشتركة في Svelte 5 هو نقلها إلى وحدة .svelte.js باستخدام $state، ثم تصدير دوال الوصول (accessors):
// counter.svelte.js
let count = $state(0);
export function getCount() {
return count;
}
export function increment() {
count++;
}
هذا يعمل لأن الـ runes صالحة خارج المكونات في ملفات .svelte.js / .svelte.ts.4
كيف تهاجر من تفاعلية Svelte 4 إلى الـ runes؟
الهجرة عملية ميكانيكية، وتوفر Svelte سكربت هجرة آلي، لكن التحويلات الأساسية هي:4
- التفاعلية
let x = 0←let x = $state(0) $: doubled = x * 2(محسوبة) ←let doubled = $derived(x * 2)$: { sideEffect(x); }(تأثير جانبي) ←$effect(() => { sideEffect(x); })export let name←let { name } = $props()export let valueمعbind:←let { value = $bindable() } = $props()
القرار التقديري الوحيد هو علامة $: القديمة، والتي كانت Svelte 4 تستخدمها لكل من القيم المحسوبة والتأثيرات الجانبية. قم بتقسيمها: أي شيء ينتج قيمة يصبح $derived؛ وأي شيء يقوم بإجراء يصبح $effect. عند الشك، اختر $derived — فهو الخيار الافتراضي الأرخص والأكثر أماناً.
الخلاصة والخطوات التالية
تحول Svelte 5 runes التفاعلية من سحر المترجم (compiler magic) إلى ثلاث أدوات صريحة: $state للقيم، و $derived للقيم المشتقة، و $effect للآثار الجانبية — مع $props/$bindable لمدخلات المكونات.58 هناك قاعدة بسيطة تتجنب فئة كاملة من الأخطاء وهي اللجوء إلى $derived قبل $effect، والتعامل مع $effect كمخرج طوارئ لأشياء مثل طلبات الشبكة وعمل الـ DOM بدلاً من كونه وسيلة لمزامنة الحالة.3
إذا كنت جديداً على إطار العمل، فابدأ بالأساسيات في دليلنا لبناء تطبيقات سريعة باستخدام Svelte، ثم ابدأ في استخدام الـ runes في ميزة حقيقية مع النماذج الآمنة برمجياً باستخدام Astro Actions. لأنماط الحالة والتنسيق الأوسع في الواجهة الأمامية، يتناسب الوضع الداكن في Tailwind v4 بشكل جيد مع مفتاح تبديل السمة المدفوع بـ $state مثل مثال $effect أعلاه.
Footnotes
-
Svelte — "Svelte 5 is alive" (stable release, October 19, 2024). https://svelte.dev/blog/svelte-5-is-alive ↩
-
Svelte — "What's new in Svelte: June 2026" (current 5.56.x line; language-tools TypeScript 6.0 support). https://svelte.dev/blog/whats-new-in-svelte-june-2026 ↩
-
Svelte Docs — "$effect" (lifecycle, dependency tracking, teardown, $effect.pre/tracking/pending/root, "When not to use $effect", untrack). https://svelte.dev/docs/svelte/$effect ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11
-
Svelte Docs — "What are runes?" and migration/stores references. https://svelte.dev/docs/svelte/what-are-runes ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Svelte Docs — "$state" (rune list in left navigation; reactive state). https://svelte.dev/docs/svelte/$state ↩ ↩2 ↩3 ↩4
-
وثائق Svelte — "$derived" (الحالة المشتقة، $derived.by، تفاعلية الدفع والسحب، التجاوز منذ الإصدار 5.25، ملاحظة البروكسي العميق). https://svelte.dev/docs/svelte/$derived ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10
-
وثائق Svelte — "$state" ($state.raw الحالة الضحلة؛ $state.snapshot نسخة منفصلة). https://svelte.dev/docs/svelte/$state ↩ ↩2
-
وثائق Svelte — "$props" (خصائص المكون عبر التفكيك مع القيم الافتراضية). https://svelte.dev/docs/svelte/$props ↩ ↩2