المرحلة الأولى (الشرح)
أبطال اقتراح TC39: دانييل إهرنبرغ، يهودا كاتز، جاتين راماناثان، شاي لويس، كريستين هيويل غاريت، دومينيك جانواي، بريستون سيجو، ميلو إم، روب أيزنبرغ
المؤلفان الأصليان: روب أيزنبرغ ودانيال إهرنبرغ
تصف هذه الوثيقة الاتجاه المشترك المبكر للإشارات في JavaScript، على غرار جهد Promises/A+ الذي سبق الوعود التي تم توحيدها بواسطة TC39 في ES2015. جربه بنفسك باستخدام مادة البوليفيل.
على غرار Promises/A+، يركز هذا الجهد على مواءمة نظام JavaScript البيئي. إذا نجحت هذه المواءمة، فمن الممكن أن يظهر معيار، بناءً على تلك التجربة. يتعاون العديد من مؤلفي إطار العمل هنا على نموذج مشترك يمكن أن يدعم جوهر تفاعلهم. تعتمد المسودة الحالية على مدخلات التصميم من المؤلفين/المشرفين على Angular وBubble وEmber وFAST وMobX وPreact وQwik وRxJS وSolid وStarbeam وSvelte وVue وWiz والمزيد...
بشكل مختلف عن Promises/A+، نحن لا نحاول إيجاد حل لواجهة برمجة تطبيقات سطحية مشتركة للمطورين، بل نحاول إيجاد دلالات أساسية دقيقة للرسم البياني للإشارة الأساسية. يتضمن هذا الاقتراح واجهة برمجة تطبيقات ملموسة بالكامل، لكن واجهة برمجة التطبيقات لا تستهدف معظم مطوري التطبيقات. بدلاً من ذلك، تعد واجهة برمجة تطبيقات الإشارة هنا مناسبة بشكل أفضل لأطر العمل للبناء عليها، مما يوفر إمكانية التشغيل البيني من خلال الرسم البياني للإشارة المشتركة وآلية التتبع التلقائي.
تتمثل خطة هذا الاقتراح في إجراء نماذج أولية مبكرة كبيرة، بما في ذلك التكامل في العديد من الأطر، قبل التقدم إلى ما بعد المرحلة الأولى. نحن مهتمون فقط بتوحيد الإشارات إذا كانت مناسبة للاستخدام العملي في أطر عمل متعددة، وتوفر فوائد حقيقية على الإطار- الإشارات المقدمة. نأمل أن توفر لنا النماذج الأولية المهمة هذه المعلومات. راجع "الحالة وخطة التطوير" أدناه لمزيد من التفاصيل.
لتطوير واجهة مستخدم معقدة (UI)، يحتاج مطورو تطبيقات JavaScript إلى تخزين الحالة وحسابها وإبطالها ومزامنتها ودفعها إلى طبقة عرض التطبيق بطريقة فعالة. تتضمن واجهات المستخدم عادة أكثر من مجرد إدارة القيم البسيطة، ولكنها غالبًا ما تتضمن عرض الحالة المحسوبة التي تعتمد على شجرة معقدة من القيم أو الحالة الأخرى التي يتم حسابها بنفسها أيضًا. الهدف من Signals هو توفير البنية التحتية لإدارة حالة التطبيق هذه حتى يتمكن المطورون من التركيز على منطق الأعمال بدلاً من هذه التفاصيل المتكررة.
تم العثور على بنيات تشبه الإشارة بشكل مستقل لتكون مفيدة في سياقات غير واجهة المستخدم أيضًا، خاصة في أنظمة البناء لتجنب عمليات إعادة البناء غير الضرورية.
تُستخدم الإشارات في البرمجة التفاعلية لإزالة الحاجة إلى إدارة التحديث في التطبيقات.
نموذج برمجة تعريفي للتحديث بناءً على التغييرات في الحالة.
من ما هو رد الفعل؟ .
بالنظر إلى المتغير counter
، فأنت تريد عرضه في DOM سواء كان العداد زوجيًا أو فرديًا. كلما تغير counter
، فأنت تريد تحديث DOM بأحدث تكافؤ. في Vanilla JS، قد يكون لديك شيء مثل هذا:
دع العداد = 0;const setCounter = (القيمة) => { العداد = القيمة؛ render();};const isEven = () => (counter & 1) == 0;const parity = () => isEven() ? "even" : "odd";const render = () => element.innerText = parity();// محاكاة التحديثات الخارجية للعداد...setInterval(() => setCounter(counter + 1), 1000);
وهذا فيه عدة مشاكل..
إعداد counter
صاخب وثقيل.
ترتبط حالة counter
بإحكام بنظام العرض.
إذا تغير counter
ولكن لم يتغير parity
(على سبيل المثال، انتقل العداد من 2 إلى 4)، فإننا نقوم بإجراء حساب غير ضروري للتكافؤ وتقديم غير ضروري.
ماذا لو كان هناك جزء آخر من واجهة المستخدم لدينا يريد فقط العرض عند تحديث counter
؟
ماذا لو كان جزء آخر من واجهة المستخدم لدينا يعتمد على isEven
أو parity
وحده؟
حتى في هذا السيناريو البسيط نسبيًا، ينشأ عدد من المشكلات بسرعة. يمكننا أن نحاول التغلب على هذه المشكلات من خلال إدخال pub/sub counter
. وهذا من شأنه أن يسمح للمستهلكين الإضافيين counter
بالاشتراك لإضافة ردود أفعالهم الخاصة على تغييرات الحالة.
ومع ذلك، ما زلنا عالقين في المشاكل التالية:
دالة التقديم، التي تعتمد فقط على parity
يجب أن "تعرف" بدلاً من ذلك أنها تحتاج بالفعل إلى الاشتراك في counter
.
ليس من الممكن تحديث واجهة المستخدم بناءً على isEven
أو parity
وحده، دون التفاعل مباشرة مع counter
.
لقد قمنا بزيادة نموذجنا. في أي وقت تستخدم فيه شيئًا ما، لا يتعلق الأمر فقط باستدعاء دالة أو قراءة متغير، بل بدلاً من ذلك الاشتراك وإجراء التحديثات هناك. تعد إدارة إلغاء الاشتراك أيضًا أمرًا معقدًا بشكل خاص.
الآن، يمكننا حل بعض المشكلات عن طريق إضافة pub/sub ليس فقط counter
ولكن أيضًا لـ isEven
و parity
. سيتعين علينا بعد ذلك الاشتراك في isEven
counter
parity
لـ isEven
render
parity
. لسوء الحظ، لم يقتصر الأمر على انفجار الكود النمطي الخاص بنا فحسب، بل نحن عالقون في الكثير من مسك دفاتر الاشتراكات، وكارثة محتملة لتسرب الذاكرة إذا لم نقم بتنظيف كل شيء بشكل صحيح بالطريقة الصحيحة. لذلك، قمنا بحل بعض المشكلات ولكننا أنشأنا فئة جديدة تمامًا من المشكلات والكثير من التعليمات البرمجية. ولجعل الأمور أسوأ، علينا أن نمر بهذه العملية برمتها لكل جزء من الدولة في نظامنا.
لطالما كانت تجريدات ربط البيانات في واجهات المستخدم للنموذج والعرض أساسية لأطر واجهة المستخدم عبر لغات برمجة متعددة، على الرغم من عدم وجود أي آلية من هذا القبيل مدمجة في JS أو منصة الويب. داخل أطر عمل JS ومكتباتها، كان هناك قدر كبير من التجارب عبر طرق مختلفة لتمثيل هذا الارتباط، وقد أظهرت التجربة قوة تدفق البيانات في اتجاه واحد جنبًا إلى جنب مع نوع بيانات من الدرجة الأولى يمثل خلية حالة أو حساب مشتقة من بيانات أخرى، تسمى الآن "الإشارات". يبدو أن نهج القيمة التفاعلية من الدرجة الأولى قد ظهر لأول مرة بشكل شائع في أطر عمل الويب JavaScript مفتوحة المصدر مع Knockout في عام 2010. وفي السنوات التي تلت ذلك، تم إنشاء العديد من الاختلافات والتطبيقات. في غضون السنوات الثلاث إلى الأربع الماضية، اكتسبت مناهج Signal البدائية والأساليب ذات الصلة المزيد من الاهتمام، حيث تحتوي كل مكتبة أو إطار عمل JavaScript حديث تقريبًا على شيء مماثل، تحت اسم أو آخر.
لفهم الإشارات، دعونا نلقي نظرة على المثال أعلاه، الذي تم إعادة تصوره باستخدام Signal API الموضح أدناه.
const counter = new Signal.State(0);const isEven = new Signal.Computed(() => (counter.get() & 1) == 0);const parity = new Signal.Computed(() => isEven .get() ? "even" : "odd");// تحدد المكتبة أو الإطار التأثيرات بناءً على تأثير دالة الإشارة الأولية الأخرى(cb: () => void): (() => void);effect(() => element.innerText = parity.get());// محاكاة التحديثات الخارجية للعداد...setInterval(() => counter.set(counter.get() + 1), 1000 );
هناك بعض الأشياء التي يمكننا رؤيتها على الفور:
لقد أزلنا النموذج النمطي المزعج حول متغير counter
من مثالنا السابق.
توجد واجهة برمجة تطبيقات موحدة للتعامل مع القيم والحسابات والآثار الجانبية.
لا توجد مشكلة مرجعية دائرية أو تبعيات مقلوبة بين counter
و render
.
لا توجد اشتراكات يدوية، ولا توجد حاجة إلى مسك الدفاتر.
هناك وسيلة للتحكم في توقيت/جدولة الآثار الجانبية.
تمنحنا الإشارات أكثر بكثير مما يمكن رؤيته على سطح واجهة برمجة التطبيقات:
التتبع التلقائي للتبعية - تكتشف الإشارة المحسوبة تلقائيًا أي إشارات أخرى تعتمد عليها، سواء كانت تلك الإشارات قيمًا بسيطة أو حسابات أخرى.
التقييم البطيء - لا يتم تقييم الحسابات بفارغ الصبر عندما يتم الإعلان عنها، ولا يتم تقييمها على الفور عندما تتغير تبعياتها. ولا يتم تقييمها إلا عندما يتم طلب قيمتها بشكل صريح.
الحفظ - تقوم الإشارات المحسوبة بتخزين قيمتها الأخيرة مؤقتًا بحيث لا تحتاج الحسابات التي ليس لديها تغييرات في تبعياتها إلى إعادة تقييم، بغض النظر عن عدد مرات الوصول إليها.
يحتوي كل تطبيق من تطبيقات Signal على آلية التتبع التلقائي الخاصة به، لتتبع المصادر التي تتم مواجهتها عند تقييم الإشارة المحسوبة. وهذا يجعل من الصعب مشاركة النماذج والمكونات والمكتبات بين أطر العمل المختلفة - فهي تميل إلى أن تأتي مع اقتران خاطئ بمحرك العرض الخاص بها (نظرًا لأن الإشارات يتم تنفيذها عادةً كجزء من أطر عمل JS).
الهدف من هذا الاقتراح هو فصل النموذج التفاعلي عن عرض العرض بشكل كامل، مما يتيح للمطورين الانتقال إلى تقنيات العرض الجديدة دون إعادة كتابة التعليمات البرمجية غير الخاصة بواجهة المستخدم، أو تطوير نماذج تفاعلية مشتركة في JS ليتم نشرها في سياقات مختلفة. لسوء الحظ، نظرًا لإصدار الإصدارات والتكرار، فقد تبين أنه من غير العملي الوصول إلى مستوى قوي من المشاركة عبر المكتبات على مستوى JS - توفر العناصر المضمنة ضمانًا أقوى للمشاركة.
يعد شحن تعليمات برمجية أقل بمثابة تعزيز صغير محتمل للأداء نظرًا لوجود مكتبات شائعة الاستخدام مدمجة، ولكن تطبيقات Signals بشكل عام صغيرة جدًا، لذلك لا نتوقع أن يكون هذا التأثير كبيرًا جدًا.
نعتقد أن تطبيقات C++ الأصلية لهياكل البيانات والخوارزميات المتعلقة بـ Signal يمكن أن تكون أكثر كفاءة قليلاً مما يمكن تحقيقه في JS، بعامل ثابت. ومع ذلك، لا يُتوقع حدوث تغييرات خوارزمية مقابل ما يمكن أن يكون موجودًا في polyfill؛ ليس من المتوقع أن تكون المحركات سحرية هنا، وستكون خوارزميات التفاعل نفسها محددة جيدًا ولا لبس فيها.
وتتوقع المجموعة البطلة تطوير تطبيقات مختلفة للإشارات واستخدامها للتحقيق في إمكانيات الأداء هذه.
مع مكتبات Signal الموجودة بلغة JS، قد يكون من الصعب تتبع أشياء مثل:
مجموعة الاستدعاءات عبر سلسلة من الإشارات المحسوبة، توضح السلسلة السببية للخطأ
الرسم البياني المرجعي بين الإشارات، عندما يعتمد أحدهما على الآخر - مهم عند تصحيح أخطاء استخدام الذاكرة
تعمل الإشارات المضمنة على تمكين أوقات تشغيل JS وDevTools من الحصول على دعم محسّن لفحص الإشارات، خاصة لتصحيح الأخطاء أو تحليل الأداء، سواء كان ذلك مدمجًا في المتصفحات أو من خلال ملحق مشترك. يمكن تحديث الأدوات الحالية مثل مفتش العناصر، ولقطات الأداء، وملفات تعريف الذاكرة لتسليط الضوء بشكل خاص على الإشارات في عرضها للمعلومات.
بشكل عام، كان لدى JavaScript مكتبة قياسية بسيطة إلى حد ما، ولكن الاتجاه في TC39 كان جعل JS أكثر من لغة "مضمنة بالبطاريات"، مع مجموعة وظائف مدمجة عالية الجودة متاحة. على سبيل المثال، يحل Temporal محل moment.js، كما يحل عدد من الميزات الصغيرة، على سبيل المثال، Array.prototype.flat
و Object.groupBy
محل العديد من حالات استخدام lodash. تشمل الفوائد أحجامًا أصغر للحزم، وتحسين الاستقرار والجودة، وتعلم القليل عند الانضمام إلى مشروع جديد، ومفردات شائعة بشكل عام عبر مطوري JS.
يسعى العمل الحالي في W3C ومن خلال منفذي المتصفح إلى جلب القوالب الأصلية إلى HTML (أجزاء DOM وإنشاء مثيل القالب). بالإضافة إلى ذلك، تستكشف W3C Web Components CG إمكانية توسيع Web Components لتقديم واجهة برمجة تطبيقات HTML تصريحية بالكامل. لتحقيق هذين الهدفين، في نهاية المطاف سوف تكون هناك حاجة إلى بدائية تفاعلية بواسطة HTML. بالإضافة إلى ذلك، يمكن تصور العديد من التحسينات المريحة على DOM من خلال تكامل الإشارات وقد طلبها المجتمع.
لاحظ أن هذا التكامل سيكون بمثابة جهد منفصل سيأتي لاحقًا، وليس جزءًا من هذا الاقتراح نفسه.
قد تكون جهود التقييس مفيدة في بعض الأحيان على مستوى "المجتمع" فقط، حتى بدون إجراء تغييرات في المتصفحات. تجمع جهود Signals بين العديد من مؤلفي إطار العمل المختلفين لإجراء مناقشة عميقة حول طبيعة التفاعل والخوارزميات وقابلية التشغيل البيني. لقد كان هذا مفيدًا بالفعل، ولا يبرر إدراجه في محركات ومتصفحات JS؛ يجب إضافة الإشارات إلى معيار JavaScript فقط إذا كانت هناك فوائد كبيرة تتجاوز تمكين تبادل معلومات النظام البيئي.
لقد اتضح أن مكتبات Signal الموجودة لا تختلف كثيرًا عن بعضها البعض، في جوهرها. ويهدف هذا الاقتراح إلى البناء على نجاحهم من خلال تطبيق الصفات المهمة للعديد من تلك المكتبات.
نوع الإشارة الذي يمثل الحالة، أي الإشارة القابلة للكتابة. هذه قيمة يمكن للآخرين قراءتها.
نوع إشارة محسوب/مذكرة/مشتق، يعتمد على أنواع أخرى ويتم حسابه وتخزينه مؤقتًا بتكاسل.
الحساب كسول، مما يعني أن الإشارات المحسوبة لا يتم حسابها مرة أخرى بشكل افتراضي عندما تتغير إحدى تبعياتها، بل يتم تشغيلها فقط إذا قرأها شخص ما بالفعل.
تعتبر عملية الحساب "خالية من الأخطاء"، مما يعني عدم إجراء أي حسابات غير ضرورية على الإطلاق. وهذا يعني أنه عندما يقرأ تطبيق إشارة محسوبة، يكون هناك فرز طوبولوجي للأجزاء التي يحتمل أن تكون قذرة في الرسم البياني ليتم تشغيلها، لإزالة أي تكرارات.
يتم تخزين الحساب مؤقتًا، مما يعني أنه إذا لم تتغير التبعيات بعد آخر مرة تغيرت فيها التبعية، فلن تتم إعادة حساب الإشارة المحسوبة عند الوصول إليها.
من الممكن إجراء مقارنات مخصصة للإشارات المحسوبة بالإضافة إلى إشارات الحالة، لملاحظة متى يجب تحديث الإشارات المحسوبة الإضافية التي تعتمد عليها.
ردود الفعل على الحالة التي تحتوي فيها الإشارة المحسوبة على إحدى تبعياتها (أو التبعيات المتداخلة) تصبح "قذرة" وتتغير، مما يعني أن قيمة الإشارة قد تكون قديمة.
يهدف رد الفعل هذا إلى جدولة عمل أكثر أهمية ليتم تنفيذه لاحقًا.
يتم تنفيذ التأثيرات من حيث ردود الفعل هذه، بالإضافة إلى الجدولة على مستوى الإطار.
تحتاج الإشارات المحسوبة إلى القدرة على الاستجابة لما إذا كانت مسجلة باعتبارها تبعية (متداخلة) لأحد هذه التفاعلات.
تمكين أطر عمل JS من القيام بالجدولة الخاصة بها. لا توجد جدولة قسرية مدمجة بأسلوب الوعد.
هناك حاجة إلى تفاعلات متزامنة لتمكين جدولة العمل لاحقًا بناءً على منطق إطار العمل.
تكون عمليات الكتابة متزامنة وتصبح سارية المفعول على الفور (يمكن لإطار العمل الذي يتم كتابته على دفعات القيام بذلك في الأعلى).
من الممكن فصل التحقق مما إذا كان التأثير "قذرًا" عن تشغيل التأثير فعليًا (تمكين جدولة التأثير على مرحلتين).
القدرة على قراءة الإشارات دون تفعيل التبعيات ليتم تسجيلها ( untrack
)
تمكين تكوين قواعد تعليمات برمجية مختلفة تستخدم الإشارات/التفاعل، على سبيل المثال،
استخدام أطر عمل متعددة معًا فيما يتعلق بالتتبع/التفاعل نفسه (إغفالات modulo، انظر أدناه)
هياكل البيانات التفاعلية المستقلة عن إطار العمل (على سبيل المثال، وكيل المتجر التفاعلي المتكرر، والخريطة التفاعلية، والمجموعة والمصفوفة، وما إلى ذلك)
تثبيط/منع سوء الاستخدام الساذج للتفاعلات المتزامنة.
خطر السلامة: قد يعرض "مواطن الخلل" إذا تم استخدامه بشكل غير صحيح: إذا تم العرض فورًا عند تعيين الإشارة، فقد يعرض حالة التطبيق غير المكتملة للمستخدم النهائي. لذلك، يجب استخدام هذه الميزة فقط لجدولة العمل بذكاء لوقت لاحق، بمجرد انتهاء منطق التطبيق.
الحل: عدم السماح بقراءة وكتابة أي إشارة من داخل رد اتصال رد الفعل المتزامن
تثبيط untrack
ووضع علامة على طبيعته غير السليمة
خطر السلامة: يسمح بإنشاء إشارات محسوبة تعتمد قيمتها على إشارات أخرى، ولكن لا يتم تحديثها عندما تتغير تلك الإشارات. ويجب استخدامه عندما لا تؤدي عمليات الوصول غير المتعقبة إلى تغيير نتيجة الحساب.
الحل: تم وضع علامة "غير آمنة" على واجهة برمجة التطبيقات (API) في الاسم.
ملحوظة: يسمح هذا الاقتراح بقراءة وكتابة الإشارات من الإشارات المحسوبة وإشارات التأثير، دون تقييد عمليات الكتابة التي تأتي بعد القراءات، على الرغم من مخاطر السلامة. تم اتخاذ هذا القرار للحفاظ على المرونة والتوافق في التكامل مع الأطر.
يجب أن تكون قاعدة صلبة لأطر العمل المتعددة لتنفيذ آليات الإشارات/التفاعل الخاصة بها.
يجب أن تكون قاعدة جيدة لبروكسيات المتجر العودية، وتفاعلية حقل الفئة القائمة على الديكور، وواجهات برمجة التطبيقات ذات النمط .value
و [state, setState]
.
الدلالات قادرة على التعبير عن الأنماط الصالحة التي تتيحها أطر العمل المختلفة. على سبيل المثال، يجب أن يكون من الممكن أن تكون هذه الإشارات أساسًا إما لعمليات الكتابة المنعكسة على الفور أو عمليات الكتابة التي يتم تجميعها وتطبيقها لاحقًا.
سيكون من الرائع أن تكون واجهة برمجة التطبيقات هذه قابلة للاستخدام مباشرة بواسطة مطوري JavaScript.
الفكرة: قم بتوفير كافة الروابط، ولكن قم بتضمين الأخطاء عند إساءة استخدامها إن أمكن.
الفكرة: ضع واجهات برمجة التطبيقات الدقيقة في مساحة اسم subtle
، تشبه crypto.subtle
، لتحديد الخط الفاصل بين واجهات برمجة التطبيقات الضرورية للاستخدام الأكثر تقدمًا مثل تنفيذ إطار عمل أو إنشاء أدوات تطوير مقابل الاستخدام اليومي لتطوير التطبيقات مثل إنشاء إشارات للاستخدام مع نطاق.
ومع ذلك، من المهم عدم تظليل نفس الأسماء حرفيًا!
إذا كانت الميزة تتطابق مع مفهوم النظام البيئي، فإن استخدام المفردات الشائعة يعد أمرًا جيدًا.
التوتر بين "سهولة الاستخدام بواسطة مطوري JS" و"توفير جميع الخطافات للأطر"
كن قابلاً للتنفيذ وقابلاً للاستخدام مع الأداء الجيد - لا تتسبب واجهة برمجة التطبيقات السطحية في حدوث الكثير من الحمل
قم بتمكين التصنيف الفرعي، بحيث يمكن للأطر إضافة الأساليب والحقول الخاصة بها، بما في ذلك الحقول الخاصة. وهذا أمر مهم لتجنب الحاجة إلى مخصصات إضافية على مستوى الإطار. انظر "إدارة الذاكرة" أدناه.
إذا كان ذلك ممكنًا: يجب أن تكون الإشارة المحسوبة قابلة للتجميع إذا لم يكن هناك شيء مباشر يشير إليها لقراءات مستقبلية محتملة، حتى لو كانت مرتبطة برسم بياني أوسع يظل حيًا (على سبيل المثال، من خلال قراءة حالة تظل حية).
لاحظ أن معظم الأطر اليوم تتطلب التخلص الصريح من الإشارات المحسوبة إذا كان لديها أي إشارة إلى أو من رسم بياني آخر للإشارة يظل حيًا.
وينتهي الأمر بأن هذا ليس سيئًا للغاية عندما يرتبط عمرها بعمر مكون واجهة المستخدم، ويجب التخلص من التأثيرات على أي حال.
إذا كان التنفيذ باستخدام هذه الدلالات مكلفًا للغاية، فيجب علينا إضافة التخلص الصريح (أو "إلغاء الارتباط") للإشارات المحسوبة إلى واجهة برمجة التطبيقات أدناه، والتي تفتقر إليها حاليًا.
هدف منفصل ذو صلة: تقليل عدد التخصيصات، على سبيل المثال،
لإنشاء إشارة قابلة للكتابة (تجنب إغلاقين منفصلين + مصفوفة)
لتنفيذ التأثيرات (تجنب الإغلاق لكل رد فعل)
في واجهة برمجة التطبيقات (API) لمراقبة تغييرات Signal، تجنب إنشاء هياكل بيانات مؤقتة إضافية
الحل: واجهة برمجة التطبيقات المستندة إلى الفصل والتي تتيح إعادة استخدام الأساليب والحقول المحددة في الفئات الفرعية
الفكرة الأولية لواجهة برمجة تطبيقات Signal موجودة أدناه. لاحظ أن هذه مجرد مسودة مبكرة، ونتوقع حدوث تغييرات بمرور الوقت. لنبدأ بـ .d.ts
الكامل للحصول على فكرة عن الشكل العام، وبعد ذلك سنناقش تفاصيل ما يعنيه كل ذلك.
واجهة الإشارة<T> {// احصل على قيمة إشارة الإشارة (): T;}إشارة مساحة الاسم {// حالة فئة الإشارة للقراءة والكتابة<T> تنفذ الإشارة<T> {// إنشاء حالة إشارة تبدأ بالقيمة tconstructor(t: T, options?: SignalOptions<T>);// احصل على قيمة signalget(): T;// اضبط قيمة إشارة الحالة على tset(t: T): void;}// إشارة وهي صيغة مبنية على أخرى Signalsclass Computed<T = غير معروف> يطبق Signal<T> {// قم بإنشاء إشارة يتم تقييمها إلى القيمة التي يتم إرجاعها بواسطة رد الاتصال. // يتم استدعاء رد الاتصال بهذه الإشارة كقيمة this.constructor(cb: (this: Computed< T>) => T, options?: SignalOptions<T>);// احصل على قيمة signalget(): T;}// تتضمن مساحة الاسم هذه ميزات "متقدمة" من الأفضل تركها لمؤلفي إطار العمل بدلاً من ذلك من مطوري التطبيقات.// مشابه لـ `crypto.subtle` مساحة الاسم الدقيقة {// قم بتشغيل رد اتصال مع تعطيل جميع وظائف التتبع untrack<T>(cb: () => T): T;// احصل على الإشارة المحسوبة الحالية والتي هي تتبع أي قراءات للإشارة، إن وجدت، الدالة currentComputed(): محسوبة | null;// يُرجع قائمة مرتبة بجميع الإشارات التي أشارت إليها هذه الإشارة// خلال آخر مرة تم تقييمها فيها.// بالنسبة للمراقب، يسرد مجموعة الإشارات التي يراقبها. (الحالة | المحسوبة)[];// إرجاع المراقبين الذين توجد بهم هذه الإشارة، بالإضافة إلى أي إشارات محسوبة قرأت هذه الإشارة في آخر مرة تم تقييمها فيها،// إذا كانت تلك الإشارة المحسوبة (بشكل متكرر) تم المشاهدة. وظيفة introspectSinks(s: الحالة | محسوبة): (محسوبة | مراقب)[];// صحيح إذا كانت هذه الإشارة "مباشرة"، حيث تتم مراقبتها بواسطة مراقب،// أو تتم قراءتها بواسطة إشارة محسوبة (بشكل متكرر) Live.function hasSinks(s: State | Computed): boolean;// صحيح إذا كان هذا العنصر "تفاعليًا"، حيث أنه يعتمد // على بعض إشارة أخرى. محسوب حيث تكون hasSources خاطئة // ستُرجع دائمًا نفس الثابت. إذا لم يتم استدعاؤها بالفعل منذ آخر مكالمة "مراقبة".// لا يجوز قراءة أو كتابة أي إشارات أثناء notify.constructor(notify: (this: Watcher) => void);// أضف هذه الإشارات إلى مجموعة المراقب، واضبط المراقب لتشغيل // إعلام رد الاتصال في المرة القادمة التي تتغير فيها أي إشارة في المجموعة (أو إحدى تبعياتها).// يمكن استدعاؤها بدون وسيطات فقط ل إعادة تعيين حالة "الإخطار"، بحيث // سيتم استدعاء رد اتصال الإخطار مرة أخرى.watch(...s: Signal[]): void;// قم بإزالة هذه الإشارات من المجموعة المراقبة (على سبيل المثال، للتأثير الذي التخلص)unwatch(...s: Signal[]): void;// إرجاع مجموعة المصادر في مجموعة Watcher التي لا تزال متسخة، أو هي إشارة محسوبة// بمصدر متسخ أو معلق ولم يتم استخدامه بعد" لم يتم إعادة تقييمها بعد getPending(): Signal[];}// خطافات لملاحظة أنك تتم مراقبتها أو لم تعد تتم مشاهدتهاvar Watched: الرمز;var unwatched: الرمز;}interface SignalOptions<T> {// وظيفة المقارنة المخصصة بين القديم والجديد قيمة. الافتراضي: Object.is.// يتم تمرير الإشارة كقيمة this لـ context.equals?: (this: Signal<T>, t: T, t2: T) => boolean;// يتم استدعاء رد الاتصال عندما يصبح isWatched صحيح، إذا كان خطأ سابقًا[Signal.subtle.watched]?: (this: Signal<T>) => void;// يتم استدعاء رد الاتصال عندما يصبح isWatched خطأ، إذا كان سابقًا صحيح[Signal.subtle.unwatched]؟: (هذا: Signal<T>) => void;}}
تمثل الإشارة خلية من البيانات التي قد تتغير بمرور الوقت. قد تكون الإشارات إما "حالة" (مجرد قيمة يتم تعيينها يدويًا) أو "محسوبة" (صيغة تعتمد على إشارات أخرى).
تعمل الإشارات المحسوبة من خلال التتبع التلقائي للإشارات الأخرى التي تتم قراءتها أثناء تقييمها. عندما تتم قراءة عملية محسوبة، فإنها تتحقق مما إذا كانت أي من تبعياتها المسجلة مسبقًا قد تغيرت، وتعيد تقييم نفسها إذا كان الأمر كذلك. عندما يتم تداخل إشارات محسوبة متعددة، فإن كل إسناد التتبع يذهب إلى الإشارة الأعمق.
الإشارات المحسوبة كسولة، أي أنها تعتمد على السحب: يتم إعادة تقييمها فقط عند الوصول إليها، حتى لو تغيرت إحدى تبعياتها في وقت سابق.
يجب أن يكون رد الاتصال الذي تم تمريره إلى الإشارات المحسوبة بشكل عام "نقيًا" بمعنى كونه وظيفة حتمية وخالية من الآثار الجانبية للإشارات الأخرى التي يصل إليها. وفي الوقت نفسه، يكون توقيت رد الاتصال الذي يتم استدعاؤه حتميًا، مما يسمح باستخدام الآثار الجانبية بحذر.
تتميز الإشارات بالتخزين المؤقت/الحفظ البارز: تتذكر إشارات الحالة والإشارات المحسوبة قيمتها الحالية، ولا تؤدي إلا إلى إعادة حساب الإشارات المحسوبة التي تشير إليها إذا تغيرت بالفعل. ليست هناك حاجة حتى إلى إجراء مقارنة متكررة بين القيم القديمة والقيم الجديدة - يتم إجراء المقارنة مرة واحدة عند إعادة تعيين/إعادة تقييم إشارة المصدر، وتقوم آلية الإشارة بتتبع الأشياء التي تشير إلى تلك الإشارة والتي لم يتم تحديثها بناءً على الجديد القيمة حتى الآن. داخليًا، يتم تمثيل ذلك بشكل عام من خلال "تلوين الرسم البياني" كما هو موضح في (منشور مدونة ميلو).
تقوم الإشارات المحسوبة بتتبع تبعياتها ديناميكيًا - في كل مرة يتم تشغيلها، قد ينتهي بها الأمر بالاعتماد على أشياء مختلفة، ويتم الاحتفاظ بمجموعة التبعيات الدقيقة هذه متجددة في الرسم البياني للإشارة. هذا يعني أنه إذا كان لديك تبعية مطلوبة على فرع واحد فقط، وأخذ الحساب السابق الفرع الآخر، فإن التغيير في تلك القيمة غير المستخدمة مؤقتًا لن يتسبب في إعادة حساب الإشارة المحسوبة، حتى عند سحبها.
على عكس وعود JavaScript، كل شيء في Signals يعمل بشكل متزامن:
يعد ضبط الإشارة على قيمة جديدة أمرًا متزامنًا، وينعكس ذلك فورًا عند قراءة أي إشارة محسوبة تعتمد عليها بعد ذلك. لا يوجد أي تجميع مدمج لهذه الطفرة.
قراءة الإشارات المحسوبة تكون متزامنة - وقيمتها متاحة دائمًا.
يتم تشغيل رد اتصال notify
في Watchers، كما هو موضح أدناه، بشكل متزامن، أثناء استدعاء .set()
الذي أدى إلى تشغيله (ولكن بعد اكتمال تلوين الرسم البياني).
مثل الوعود، يمكن أن تمثل الإشارات حالة خطأ: إذا حدث رد اتصال محسوب للإشارة، فسيتم تخزين هذا الخطأ مؤقتًا تمامًا مثل قيمة أخرى، ويتم إعادة طرحه في كل مرة تتم فيها قراءة الإشارة.
يمثل مثيل Signal
القدرة على قراءة قيمة متغيرة ديناميكيًا يتم تتبع تحديثاتها بمرور الوقت. ويتضمن أيضًا ضمنيًا القدرة على الاشتراك في الإشارة، ضمنيًا من خلال وصول متعقب من إشارة محسوبة أخرى.
تم تصميم واجهة برمجة التطبيقات هنا لتتوافق مع الإجماع التقريبي للنظام البيئي بين جزء كبير من مكتبات Signal في استخدام أسماء مثل "الإشارة" و"المحسوبة" و"الحالة". ومع ذلك، يتم الوصول إلى الإشارات المحسوبة وإشارات الحالة من خلال طريقة .get()
، والتي لا تتوافق مع جميع واجهات برمجة التطبيقات Signal الشائعة، والتي تستخدم إما ملحق نمط .value
، أو بناء جملة استدعاء signal()
.
تم تصميم واجهة برمجة التطبيقات (API) لتقليل عدد التخصيصات، لجعل الإشارات مناسبة للتضمين في أطر عمل JavaScript مع الوصول إلى نفس الأداء أو أداء أفضل من الإشارات المخصصة لإطار العمل الحالي. وهذا يعني:
إشارات الحالة هي كائن واحد قابل للكتابة، ويمكن الوصول إليه وتعيينه من نفس المرجع. (راجع الآثار الواردة أدناه في قسم "فصل القدرات".)
تم تصميم كل من إشارات الحالة والإشارات المحوسبة لتكون قابلة للتصنيف الفرعي، لتسهيل قدرة الأطر على إضافة خصائص إضافية من خلال حقول الفئات العامة والخاصة (بالإضافة إلى طرق استخدام تلك الحالة).
يتم استدعاء عمليات رد الاتصال المختلفة (على سبيل المثال، equals
رد الاتصال المحسوب) باستخدام الإشارة ذات الصلة كقيمة this
للسياق، بحيث لا تكون هناك حاجة إلى إغلاق جديد لكل إشارة. وبدلاً من ذلك، يمكن حفظ السياق في خصائص إضافية للإشارة نفسها.
بعض شروط الخطأ التي تفرضها واجهة برمجة التطبيقات هذه:
ومن الخطأ قراءة محسوبة بشكل متكرر.
لا يمكن لرد اتصال notify
الخاص بالمراقب قراءة أو كتابة أي إشارات
إذا تم إطلاق رد الاتصال الخاص بإشارة محسوبة، فسيتم الوصول لاحقًا إلى إعادة إلقاء الخطأ الذي تم تخزينه مؤقتًا، حتى تتغير إحدى التبعيات ويتم إعادة حسابها.
بعض الشروط التي لا يتم تنفيذها:
يمكن للإشارات المحسوبة أن تكتب إلى إشارات أخرى بشكل متزامن ضمن رد الاتصال الخاص بها
قد يقرأ العمل الذي يتم وضعه في قائمة الانتظار من خلال رد اتصال notify
المراقب الإشارات أو يكتبها، مما يجعل من الممكن تكرار أنماط React الكلاسيكية المضادة فيما يتعلق بالإشارات!
توفر واجهة Watcher
المحددة أعلاه الأساس لتنفيذ واجهات برمجة تطبيقات JS النموذجية للتأثيرات: عمليات الاسترجاعات التي يتم إعادة تشغيلها عندما تتغير الإشارات الأخرى، فقط من أجل آثارها الجانبية. يمكن تعريف وظيفة effect
المستخدمة أعلاه في المثال الأولي على النحو التالي:
// ستعيش هذه الوظيفة عادةً في مكتبة/إطار عمل، وليس في رمز التطبيق// ملاحظة: منطق الجدولة هذا أساسي جدًا بحيث لا يكون مفيدًا. لا تنسخ/تلصق.let hanging = false;let w = new Signal.subtle.Watcher(() => {if (!pending) {pending = true;queueMicrotask(() => {pending = false;for (let s of w.getPending()) s.get();w.watch();});}});// إشارة تأثير التأثير التي يتم تقييمها إلى cb، والتي تقوم بجدولة قراءة // نفسها في قائمة انتظار المهام الصغيرة عندما قد تتغير إحدى تبعياتها تأثير وظيفة التصدير (cb) {let destructor;let c = new Signal.Computed(() => { destructor?.(); destructor = cb(); });w.watch(c) ;c.get();return () => { destructor?.(); w.unwatch(c) };}
لا تتضمن Signal API أي وظيفة مدمجة مثل effect
. وذلك لأن جدولة التأثير دقيقة وغالبًا ما ترتبط بدورات عرض إطار العمل وغيرها من الحالات أو الاستراتيجيات عالية المستوى الخاصة بإطار العمل والتي لا يمكن لـ JS الوصول إليها.
من خلال العمليات المختلفة المستخدمة هنا: رد اتصال notify
الذي تم تمريره إلى مُنشئ Watcher
هو الوظيفة التي يتم استدعاؤها عندما تنتقل الإشارة من حالة "نظيفة" (حيث نعلم أن ذاكرة التخزين المؤقت قد تمت تهيئتها وصالحة) إلى حالة "محددة" أو "قذرة" "الحالة (حيث قد تكون ذاكرة التخزين المؤقت صالحة أو لا تكون صالحة لأنه تم تغيير حالة واحدة على الأقل من الحالات التي يعتمد عليها هذا بشكل متكرر).
يتم تشغيل مكالمات notify
في النهاية عن طريق استدعاء .set()
في بعض إشارات الحالة. هذه المكالمة متزامنة: فهي تحدث قبل إرجاع .set
. ولكن لا داعي للقلق بشأن رد الاتصال هذا بمراقبة الرسم البياني للإشارة في حالة نصف معالجة، لأنه أثناء رد اتصال notify
، لا يمكن قراءة أو كتابة إشارة، حتى في مكالمة untrack
. نظرًا لاستدعاء notify
أثناء .set()
، فإنه يقاطع خيطًا منطقيًا آخر، والذي قد لا يكون مكتملًا. لقراءة الإشارات أو كتابتها من notify
، قم بجدولة العمل ليتم تشغيله لاحقًا، على سبيل المثال، عن طريق كتابة الإشارة في قائمة ليتم الوصول إليها لاحقًا، أو باستخدام queueMicrotask
كما هو مذكور أعلاه.
لاحظ أنه من الممكن تمامًا استخدام الإشارات بشكل فعال دون استخدام Symbol.subtle.Watcher
عن طريق جدولة استقصاء الإشارات المحسوبة، كما يفعل Glimmer. ومع ذلك، فقد وجدت العديد من أطر العمل أنه من المفيد في كثير من الأحيان تشغيل منطق الجدولة هذا بشكل متزامن، وبالتالي فإن Signals API تتضمنه.
يتم جمع كل من الإشارات المحسوبة وإشارات الحالة مثل أي قيم JS. لكن لدى المراقبين طريقة خاصة لإبقاء الأمور على قيد الحياة: أي إشارات يراقبها المراقب ستظل حية طالما كان من الممكن الوصول إلى أي من الحالات الأساسية، حيث قد يؤدي ذلك إلى إطلاق مكالمة notify
مستقبلية (ومن ثم .get()
مستقبلية .get()
). لهذا السبب، تذكر الاتصال بـ Watcher.prototype.unwatch
لتنظيف التأثيرات.
Signal.subtle.untrack
عبارة عن فتحة هروب تسمح بقراءة الإشارات دون تتبع تلك القراءات. هذه الإمكانية غير آمنة لأنها تسمح بإنشاء إشارات محسوبة تعتمد قيمتها على إشارات أخرى، ولكن لا يتم تحديثها عندما تتغير تلك الإشارات. ويجب استخدامه عندما لا تؤدي عمليات الوصول غير المتعقبة إلى تغيير نتيجة الحساب.
يمكن إضافة هذه الميزات لاحقًا، لكن لم يتم تضمينها في المسودة الحالية. يرجع إغفالها إلى عدم وجود إجماع راسخ في مساحة التصميم بين الأطر، بالإضافة إلى القدرة الواضحة على التغلب على غيابها باستخدام آليات أعلى فكرة الإشارات الموضحة في هذه الوثيقة. ومع ذلك، لسوء الحظ، فإن هذا الإغفال يحد من إمكانية التشغيل البيني بين الأطر. ومع إنتاج نماذج أولية للإشارات كما هو موضح في هذه الوثيقة، سيكون هناك جهد لإعادة النظر فيما إذا كان هذا الإغفال هو القرار المناسب.
غير متزامن : الإشارات متاحة دائمًا بشكل متزامن للتقييم، في هذا النموذج. ومع ذلك، فمن المفيد في كثير من الأحيان أن يكون لديك عمليات غير متزامنة معينة تؤدي إلى تعيين الإشارة، وأن يكون لديك فهم عندما تكون الإشارة لا تزال في "التحميل". إحدى الطرق البسيطة لنمذجة حالة التحميل هي الاستثناءات، ويتوافق سلوك تخزين الاستثناءات المؤقت للإشارات المحسوبة بشكل معقول إلى حد ما مع هذه التقنية. تمت مناقشة التقنيات المحسنة في العدد رقم 30.
المعاملات : بالنسبة للتحولات بين وجهات النظر ، من المفيد في كثير من الأحيان الحفاظ على حالة حية لكل من الحالات "من" و "إلى". تقدم الدولة "إلى" في الخلفية ، حتى تصبح جاهزة للتبديل (ارتكاب المعاملة) ، بينما تظل الدولة "من" تفاعلية. يتطلب الحفاظ على كلتا الدولتين في نفس الوقت "صقل" حالة الرسم البياني للإشارة ، وقد يكون من المفيد دعم التحولات المتعددة المعلقة في وقت واحد. المناقشة في العدد رقم 73.
يتم أيضًا حذف بعض أساليب الراحة المحتملة.
هذا الاقتراح موجود على جدول أعمال TC39 في أبريل 2024 للمرحلة الأولى. يمكن اعتباره حاليًا "المرحلة 0".
يتوفر polyfill لهذا الاقتراح ، مع بعض الاختبارات الأساسية. بدأ بعض مؤلفي الإطارات في تجربة استبدال تطبيق الإشارة هذا ، ولكن هذا الاستخدام في مرحلة مبكرة.
يريد المتعاونون في اقتراح الإشارة أن يكونوا محافظين بشكل خاص في كيفية دفع هذا الاقتراح إلى الأمام ، حتى لا نلقي في فخ الحصول على شيء يتم شحنه ، وينتهي بنا الأمر إلى الأسف ولا نستخدمه فعليًا. خطتنا هي القيام بالمهام الإضافية التالية ، غير المطلوبة من خلال عملية TC39 ، للتأكد من أن هذا الاقتراح على المسار الصحيح:
قبل اقتراح المرحلة 2 ، نخطط ل:
قم بتطوير تطبيقات متعددة من الدرجة المتعددة من فئة الإنتاج والتي تتسم بالمواصفات بشكل جيد (على سبيل المثال ، اختبارات تمرير من أطر عمل مختلفة بالإضافة إلى اختبارات على غرار 262) ، والتنافسية من حيث الأداء (كما تم التحقق منها بمجموعة مرجعية شاملة للإشارة/الإطار).
دمج واجهة برمجة تطبيقات الإشارة المقترحة في عدد كبير من أطر JS التي نعتبرها تمثيلية إلى حد ما ، وبعض التطبيقات الكبيرة تعمل مع هذا الأساس. اختبر أنه يعمل بكفاءة وبشكل صحيح في هذه السياقات.
لديك فهم قوي حول مساحة الامتدادات المحتملة إلى واجهة برمجة التطبيقات ، وخلصت إلى ما الذي يجب إضافته (إن وجد) إلى هذا الاقتراح.
يصف هذا القسم كل من واجهات برمجة التطبيقات المعرضة لـ JavaScript ، من حيث الخوارزميات التي تنفذها. يمكن اعتبار ذلك بمثابة تصنيف بروتو ، ويتم تضمينه في هذه النقطة المبكرة لتخفيض مجموعة واحدة من الدلالات ، بينما تكون مفتوحة للغاية للتغييرات.
بعض جوانب الخوارزمية:
يعد ترتيب قراءات الإشارات داخل المحسوبة أمرًا مهمًا ، ويمكن ملاحظته بالترتيب الذي يتم فيه استدعاء بعض عمليات الاسترجاعات (التي يتم استدعاء Watcher
، equals
، والمعلمة الأولى إلى new Signal.Computed
، وإعداد عمليات الاسترداد watched
/ unwatched
). هذا يعني أنه يجب تخزين مصادر الإشارة المحسوبة.
قد ترمي جميع عمليات الاسترجاعات الأربعة استثناءات ، ويتم نشر هذه الاستثناءات بطريقة يمكن التنبؤ بها لرمز الاتصال JS. لا تقطع الاستثناءات تنفيذ هذه الخوارزمية أو تترك الرسم البياني في حالة نصف معالجة. بالنسبة للأخطاء التي ألقيت في notify
باستدعاء المراقب ، يتم إرسال هذا الاستثناء إلى مكالمة .set()
التي أثارت ذلك ، باستخدام AggregateRror إذا تم طرح استثناءات متعددة. يتم تخزين الآخرين (بما في ذلك watched
/ unwatched
؟) في قيمة الإشارة ، ليتم إعادة صياغتها عند القراءة ، ويمكن وضع علامة على هذه الإشارة المتساقطة ~clean~
تمامًا مثل أي قيمة أخرى ذات قيمة طبيعية.
يتم توخي الحذر لتجنب التعميمات في حالات الإشارات المحسوبة التي لا تتم مراقبتها (التي يلاحظها أي مراقب) ، بحيث يمكن جمع القمامة بشكل مستقل عن أجزاء أخرى من الرسم البياني للإشارة. داخليًا ، يمكن تنفيذ ذلك باستخدام نظام من أرقام التوليد التي يتم جمعها دائمًا ؛ لاحظ أن التطبيقات المحسّنة قد تتضمن أيضًا أرقام توليد كل عقدة محلية ، أو تجنب تتبع بعض الأرقام على الإشارات المراقبة.
تحتاج خوارزميات الإشارة إلى الرجوع إلى حالة عالمية معينة. هذه الحالة عالمية للخيط بأكمله ، أو "الوكيل".
computing
: يتم إعادة .run
إشارة الأعمق المحسوبة أو التأثير حاليًا بسبب .get
null
في البداية null
.
frozen
: Boolean يدل على ما إذا كان هناك رد اتصال ينفذ حاليًا والذي يتطلب عدم تعديل الرسم البياني. false
في البداية.
generation
: عدد صحيح متزايد ، بدءًا من 0 ، يستخدم لتتبع مدى قيمة التيار أثناء تجنب التعميمات.
Signal
Signal
هي كائن عادي يعمل كمساحة اسم للفئات والوظائف المتعلقة بالإشارة.
Signal.subtle
هو كائن مساحة اسم داخلي مماثل.
Signal.State
ClassSignal.State
فتحات داخلية value
: القيمة الحالية لإشارة الحالة
equals
: وظيفة المقارنة المستخدمة عند تغيير القيم
watched
: رد الاتصال المطلوب استدعاؤه عندما تصبح الإشارة الملاحظة بتأثير
unwatched
: رد الاتصال الذي يتم استدعاؤه عندما لم يعد يتم ملاحظة الإشارة بتأثير ما
sinks
: مجموعة من الإشارات المراقبة التي تعتمد على هذا الإشارات
Signal.State(initialValue, options)
اضبط value
هذه الإشارة على initialValue
.
اضبط هذه الإشارة equals
مع الخيارات؟
قم بتعيين watched
هذه الإشارة على الخيارات؟ [Signal.Subtle.Watched]
قم بتعيين هذه الإشارة unwatched
بالخيارات؟
اضبط sinks
هذه الإشارة على المجموعة الفارغة
Signal.State.prototype.get()
إذا كان frozen
صحيحًا ، فقم بإلقاء استثناء.
إذا لم تكن computing
undefined
، أضف هذه الإشارة إلى مجموعة sources
computing
.
ملاحظة: لا نضيف computing
إلى مجموعة sinks
هذه الإشارة حتى يتم مشاهدتها بواسطة مراقب.
إرجاع value
هذه الإشارة.
Signal.State.prototype.set(newValue)
إذا frozen
سياق التنفيذ الحالي ، فقم بإلقاء استثناء.
قم بتشغيل خوارزمية "Set Signal Value" مع هذه الإشارة والمعلمة الأولى للقيمة.
إذا عادت تلك الخوارزمية ~clean~
، فأعود غير محدد.
اضبط state
جميع sinks
هذه الإشارة على (إذا كانت إشارة محسوبة) ~dirty~
إذا كانت نظيفة من قبل ، أو (إذا كانت مراقب) ~pending~
إذا كان من قبل ~watching~
.
قم بتعيين state
جميع تبعيات الإشارة المحسوبة للمصارف (بشكل متكرر) إلى ~checked~
إذا كانت ~clean~
(أي ، اترك العلامات القذرة في مكانها) ، أو للمشاهد ، ~pending~
إذا كانت ~watching~
.
لكل ~watching~
مراقب واجهته في هذا البحث العودية ، ثم بترتيب عمق أول ،
تعيين frozen
إلى حقيقية.
استدعاء رد الاتصال notify
بهم (توفير أي استثناء جانبا ، ولكن تجاهل قيمة الإرجاع notify
).
استعادة frozen
إلى خطأ.
اضبط state
المراقب على ~waiting~
.
إذا تم إلقاء أي استثناء من عمليات الاسترجاعات notify
، فقم بنشره إلى المتصل بعد تشغيل جميع عمليات notify
. إذا كان هناك استثناءات متعددة ، فحين تجميعها معًا في مجموعة كبيرة ورمي ذلك.
العودة غير محددة.
Signal.Computed
Signal.Computed
Putted Machine قد تكون state
الإشارة المحسوبة واحدة مما يلي:
~clean~
: قيمة الإشارة موجودة ومعروفة لا تكون قديمة.
~checked~
: لقد تغير مصدر (غير مباشر) لهذه الإشارة ؛ هذه الإشارة لها قيمة لكنها قد تكون قديمة. ما إذا كان أو لم يكن قائما معروفًا فقط عندما يتم تقييم جميع المصادر الفورية.
~computing~
: يتم تنفيذ رد اتصال هذه الإشارة حاليًا كنتيجة جانبية لمكالمة .get()
.
~dirty~
: إما أن هذه الإشارة لها قيمة معروفة بأنها قديمة ، أو لم يتم تقييمها أبدًا.
الرسم البياني الانتقالي كما يلي:
مخطط الحالة-v2
[*] -> القذرة
القذرة -> الحوسبة: [4]
الحوسبة -> نظيفة: [5]
نظيفة -> قذرة: [2]
نظيف -> فحص: [3]
فحص -> نظيف: [6]
تم التحقق منه -> قذر: [1]
تحميلالتحولات هي:
رقم | من | ل | حالة | خوارزمية |
---|---|---|---|---|
1 | ~checked~ | ~dirty~ | تم تقييم مصدر فوري لهذه الإشارة ، وهي إشارة محسوبة ، وتغيرت قيمتها. | الخوارزمية: إعادة حساب الإشارة المحسوبة القذرة |
2 | ~clean~ | ~dirty~ | تم تعيين مصدر فوري لهذه الإشارة ، وهي حالة ، بقيمة لا تساوي قيمتها السابقة. | الطريقة: Signal.State.prototype.set(newValue) |
3 | ~clean~ | ~checked~ | تم تعيين مصدر متكرر ، ولكن ليس فوريًا ، وهي حالة ، مع قيمة لا تساوي قيمتها السابقة. | الطريقة: Signal.State.prototype.set(newValue) |
4 | ~dirty~ | ~computing~ | نحن على وشك تنفيذ callback . | الخوارزمية: إعادة حساب الإشارة المحسوبة القذرة |
5 | ~computing~ | ~clean~ | انتهى callback من تقييم وإعادة قيمة أو ألقيت استثناء. | الخوارزمية: إعادة حساب الإشارة المحسوبة القذرة |
6 | ~checked~ | ~clean~ | تم تقييم جميع المصادر الفورية لهذه الإشارة ، وقد تم اكتشاف جميعها دون تغيير ، لذلك نحن الآن معروفون أننا لا نكون قديمة. | الخوارزمية: إعادة حساب الإشارة المحسوبة القذرة |
Signal.Computed
فتحات داخلية value
: القيمة المخبأة السابقة للإشارة ، أو ~uninitialized~
لإشارة محسوبة أبدًا. قد تكون القيمة استثناءًا يتم إعادة تشغيله عند قراءة القيمة. دائما undefined
لإشارات التأثير.
state
: قد تكون ~clean~
، ~checked~
، ~computing~
، أو ~dirty~
.
sources
: مجموعة من الإشارات المرتبة التي تعتمد عليها هذه الإشارة.
sinks
: مجموعة مرتبة من الإشارات التي تعتمد على هذه الإشارة.
equals
: طريقة متساوية المقدمة في الخيارات.
callback
: رد الاتصال الذي يتم استدعاؤه للحصول على قيمة الإشارة المحسوبة. تم تعيينه على المعلمة الأولى التي تم تمريرها إلى المُنشئ.
Signal.Computed
Constructorمجموعات المنشئ
callback
إلى المعلمة الأولى
equals
بناءً على الخيارات ، والتخلف عن Object.is
إذا كان غائبًا
state
إلى ~dirty~
value
إلى ~uninitialized~
مع AsyncContext ، تم نقل رد الاتصال إلى new Signal.Computed
على اللقطة من عندما تم استدعاء المُنشئ ، ويستعيد هذه اللقطة أثناء تنفيذها.
Signal.Computed.prototype.get
إذا تم frozen
سياق التنفيذ الحالي أو إذا كانت هذه الإشارة تحتوي على الحالة ~computing~
، أو إذا كانت هذه الإشارة تأثيرًا computing
إشارة محسوبة ، فقم بإلقاء استثناء.
إذا لم تكن computing
null
، فأضف هذه الإشارة إلى مجموعة sources
computing
.
ملاحظة: لا نضيف computing
إلى sinks
هذه الإشارة حتى/ما لم يتم مراقبتها بواسطة مراقب.
إذا كانت حالة هذه الإشارة ~dirty~
أو ~checked~
: كرر الخطوات التالية حتى تكون هذه الإشارة ~clean~
:
تكرر عن طريق sources
للعثور على المصدر العودية الأعمق واليسرى (أي أقدم) المصدر المتكرر وهو إشارة محسوبة ملحوظة ~dirty~
(قطع البحث عند ضرب إشارة ~clean~
محسوبة ، بما في ذلك هذه الإشارة المحسوبة كآخر شيء للبحث).
قم بتنفيذ خوارزمية "إعادة حساب الإشارة المحسوبة القذرة" على تلك الإشارة.
في هذه المرحلة ، ستكون حالة هذه الإشارة ~clean~
، ولن تكون هناك مصادر متكررة ~dirty~
أو ~checked~
. إرجاع value
الإشارة. إذا كانت القيمة استثناء ، فالتعويض عن هذا الاستثناء.
Signal.subtle.Watcher
ClassSignal.subtle.Watcher
Machine قد تكون state
المراقب واحدة مما يلي:
~waiting~
: تم تشغيل رد notify
، أو أن المراقب جديد ، لكنه لا يشاهد أي إشارات بنشاط.
~watching~
: يراقب المراقب إشارات نشطة ، ولكن لم تحدث أي تغييرات بعد والتي تستلزم notify
رد الاتصال.
~pending~
: لقد تغيرت تبعية المراقب ، لكن notify
لم يتم تشغيل رد الاتصال بعد.
الرسم البياني الانتقالي كما يلي:
مخطط الحالة-v2
[*] -> الانتظار
الانتظار -> مشاهدة: [1]
مشاهدة -> الانتظار: [2]
مشاهدة -> معلق: [3]
معلق -> الانتظار: [4]
تحميلالتحولات هي:
رقم | من | ل | حالة | خوارزمية |
---|---|---|---|---|
1 | ~waiting~ | ~watching~ | تم استدعاء طريقة watch المراقب. | الطريقة: Signal.subtle.Watcher.prototype.watch(...signals) |
2 | ~watching~ | ~waiting~ | تم استدعاء طريقة unwatch المراقبين ، وتمت إزالة آخر إشارة مراقبة. | الطريقة: Signal.subtle.Watcher.prototype.unwatch(...signals) |
3 | ~watching~ | ~pending~ | قد تكون إشارة مراقبة قد تغيرت القيمة. | الطريقة: Signal.State.prototype.set(newValue) |
4 | ~pending~ | ~waiting~ | تم تشغيل notify رد الاتصال. | الطريقة: Signal.State.prototype.set(newValue) |
Signal.subtle.Watcher
فتحات داخلية state
: قد تكون ~watching~
، ~pending~
أو ~waiting~
signals
: مجموعة من الإشارات المرتبة التي يراقبها هذا المراقب
notifyCallback
: رد الاتصال الذي يسمى عندما يتغير شيء ما. تم تعيينه على المعلمة الأولى التي تم تمريرها إلى المُنشئ.
new Signal.subtle.Watcher(callback)
تم تعيين state
على ~waiting~
.
تهيئة signals
كمجموعة فارغة.
يتم تعيين notifyCallback
على معلمة رد الاتصال.
مع AsyncContext ، انتقل رد الاتصال إلى new Signal.subtle.Watcher
.
Signal.subtle.Watcher.prototype.watch(...signals)
إذا كان frozen
صحيحًا ، فقم بإلقاء استثناء.
إذا لم يكن أي من الحجج إشارة ، فابحث عن استثناء.
قم بإلحاق جميع الوسائط بنهاية signals
هذا الكائن.
لكل إشارة مراقبة حديثًا ، بترتيب من اليسار إلى اليمين ،
أضف هذا المراقب sink
لتلك الإشارة.
إذا كان هذا هو الحوض الأول ، ثم تكرر إلى مصادر لإضافة تلك الإشارة كوعة.
تعيين frozen
إلى حقيقية.
استدعاء رد الاتصال watched
إذا كان موجودًا.
استعادة frozen
إلى خطأ.
إذا كانت state
الإشارة ~waiting~
، فقم بتعيينها على ~watching~
.
Signal.subtle.Watcher.prototype.unwatch(...signals)
إذا كان frozen
صحيحًا ، فقم بإلقاء استثناء.
إذا لم يكن أي من الحجج إشارة ، أو لا يتم مشاهدته من قبل هذا المراقب ، فابحث عن استثناء.
لكل إشارة في الحجج ، بترتيب من اليسار إلى اليمين ،
قم بإزالة هذه الإشارة من مجموعة signals
هذا المراقب.
أخرج مراقب هذه المجموعة من مجموعة sink
.
إذا أصبحت مجموعة sink
هذه الإشارة فارغة ، فقم بإزالة هذه الإشارة كوعة من كل مصادرها.
تعيين frozen
إلى حقيقية.
استدعاء رد الاتصال unwatched
إذا كان موجودًا.
استعادة frozen
إلى خطأ.
إذا لم يكن لدى المراقب الآن أي signals
، state
~watching~
، ثم قم بتعيينه على ~waiting~
.
Signal.subtle.Watcher.prototype.getPending()
إرجاع صفيف يحتوي على مجموعة فرعية من signals
التي يتم حسابها في الحالات ~dirty~
أو ~pending~
.
Signal.subtle.untrack(cb)
دع c
يكون حالة computing
الحالية لسياق التنفيذ.
ضبط computing
على NULL.
استدعاء cb
.
استعادة computing
إلى c
(حتى لو ألقى cb
استثناء).
إرجاع قيمة الإرجاع لـ cb
(إعادة أي استثناء).
ملاحظة: UNTRACK لا يخرجك من الحالة frozen
، والتي يتم الحفاظ عليها بدقة.
Signal.subtle.currentComputed()
إرجاع قيمة computing
الحالية.
امسح sources
هذه الإشارة مجموعة ، وأزلها من sinks
تلك المصادر.
احفظ قيمة computing
السابقة وضبط computing
على هذه الإشارة.
اضبط حالة هذه الإشارة على ~computing~
.
قم بتشغيل رد اتصال هذه الإشارة المحسوبة ، باستخدام هذه الإشارة مثل هذه القيمة. احفظ قيمة الإرجاع ، وإذا ألقى رد الاتصال استثناءً ، فقم بتخزينه من أجل إعادة التثبيت.
استعادة قيمة computing
السابقة.
قم بتطبيق خوارزمية "Set Signal Value" على قيمة إرجاع رد الاتصال.
اضبط حالة هذه الإشارة على ~clean~
.
إذا تم إرجاع هذه الخوارزمية ~dirty~
: حدد جميع أحواض هذه الإشارة على أنها ~dirty~
(سابقًا ، قد تكون المصارف مزيجًا من الفحص والقذرة). (أو ، إذا كان هذا غير مستقر ، فقم بتبني رقم جيل جديد للإشارة إلى الأوساخ ، أو شيء من هذا القبيل.)
خلاف ذلك ، عادت هذه الخوارزمية ~clean~
: في هذه الحالة ، لكل ~checked~
بالوعة من هذه الإشارة ، إذا كانت جميع مصادر هذه الإشارة نظيفة الآن ، ثم حدد تلك الإشارة على أنها ~clean~
أيضًا. قم بتطبيق خطوة التنظيف هذه لمزيد من المصارير بشكل متكرر ، على أي إشارات نظيفة حديثًا والتي تحقق من المصارف. (أو ، إذا كان هذا غير مستقر ، يشير بطريقة ما إلى ذلك ، بحيث يمكن للتنظيف المضي قدماً بتكاسل.)
إذا تم تمرير هذه الخوارزمية قيمة (على عكس استثناء لإعادة التزحلق ، من خوارزمية الإشارة المحسوبة القذرة):
استدعاء وظيفة equals
هذه الإشارة ، تمرير كمعلمات value
الحالية ، القيمة الجديدة ، وهذه الإشارة. إذا تم طرح استثناء ، احفظ هذا الاستثناء (لإعادة القراءة) كقيمة للإشارة واستمر كما لو أن رد الاتصال قد عاد خطأ.
إذا عادت هذه الوظيفة بشكل صحيح ، فأعود ~clean~
.
اضبط value
هذه الإشارة على المعلمة.
العودة ~dirty~
س : أليس من المقترب قليلاً أن تقوم بتوحيد شيء يتعلق بالإشارات ، عندما بدأوا للتو في أن يكون الشيء الجديد الساخن في عام 2022؟ ألا يجب أن نمنحهم المزيد من الوقت للتطور والاستقرار؟
ج : الوضع الحالي للإشارات في أطر عمل الويب هو نتيجة لأكثر من 10 سنوات من التطوير المستمر. مع ارتفاع الاستثمار ، كما كان في السنوات الأخيرة ، تقترب جميع أطر عمل الويب تقريبًا من نموذج أساسي مشابه جدًا للإشارات. هذا الاقتراح هو نتيجة لتمرين تصميم مشترك بين عدد كبير من القادة الحاليين في أطر عمل الويب ، ولن يتم دفعه إلى الأمام إلى التقييس دون التحقق من صحة هذه المجموعة من خبراء المجال في سياقات مختلفة.
س : هل يمكن استخدام الإشارات المدمجة من قبل الأطر ، بالنظر إلى تكاملها الضيق مع التقديم والملكية؟
ج : تميل الأجزاء التي هي أكثر خاصة بالإطار إلى أن تكون في مجال التأثيرات والجدولة والملكية/التخلص منها ، والتي لا يحاول هذا الاقتراح حلها. تتمثل أولويتنا الأولى في الإشارات في مجال معايير النماذج الأولية في التحقق من صحة أنه يمكنهم الجلوس "أسفل" الأطر الحالية بشكل متوافق وبأداء جيد.
س : هل من المفترض أن يتم استخدام API للإشارة مباشرة من قبل مطوري التطبيقات ، أو ملفوفة بواسطة الأطر؟
ج : في حين يمكن استخدام API هذه مباشرة من قبل مطوري التطبيقات (على الأقل الجزء الذي ليس ضمن مساحة Signal.subtle
. بدلاً من ذلك ، فإن احتياجات مؤلفي المكتبة/الإطار هي أولويات. من المتوقع أن تلتزم معظم الأطر حتى Signal.State
الأساسية Signal.Computed
في الممارسة العملية ، من الأفضل عادةً استخدام الإشارات عبر إطار عمل ، والذي يدير ميزات أكثر صعوبة (على سبيل المثال ، مراقب ، untrack
) ، بالإضافة إلى إدارة الملكية والتخلص (على سبيل المثال ، معرفة متى يجب إضافة الإشارات وإزالتها من المراقبين) ، و الجدولة التقديم إلى DOM- هذا الاقتراح لا يحاول حل هذه المشكلات.
س : هل يجب علي هدم الإشارات المتعلقة بعناية عندما يتم تدمير تلك القطعة؟ ما هي واجهة برمجة التطبيقات لذلك؟
ج : عملية teardown ذات الصلة هنا هي Signal.subtle.Watcher.prototype.unwatch
. يجب تنظيف الإشارات التي تمت مراقبتها فقط (عن طريق عدم إهمالها) ، في حين يمكن أن يتم جمع الإشارات غير المألوفة تلقائيًا.
س : هل تعمل الإشارات مع VDOM ، أو مباشرة مع HTML DOM؟
ج : نعم! الإشارات مستقلة عن التقديم. تتكامل أطر JavaScript الحالية التي تستخدم بنيات تشبه الإشارة مع VDOM (على سبيل المثال ، PREACT) ، DOM الأصلي (على سبيل المثال ، الصلبة) ومجموعة (على سبيل المثال ، VUE). الشيء نفسه سيكون ممكنا مع الإشارات المدمجة.
س : هل سيكون من المريح استخدام إشارات في سياق الأطر القائمة على الفصل مثل Angular و Lit؟ ماذا عن الأطر القائمة على المترجم مثل سفيلتي؟
ج : يمكن صنع حقول الفئة على أساس إشارة مع ديكور ملحق بسيط ، كما هو موضح في الإشارة polyfill readme. تتم محاذاة الإشارات بشكل وثيق مع رونية Svelte 5-من السهل بالنسبة للمترجم تحويل الرونية إلى API الإشارة المحددة هنا ، وفي الواقع هذا ما يفعله Svelte 5 داخليًا (ولكن مع مكتبة الإشارات الخاصة به).
س : هل تعمل الإشارات مع SSR؟ ترطيب؟ استئناف؟
ج : نعم. يستخدم QWIK إشارات لتأثير جيد مع كل من هذه الخصائص ، والأطر الأخرى لها مناهج أخرى متطورة للترطيب مع إشارات ذات مقايضات مختلفة. نعتقد أنه من الممكن تصميم إشارات Qwik التي يمكن استئنافها باستخدام حالة وإشارة محسوبة مدمجة معًا ، والتخطيط لإثبات ذلك في الكود.
س : هل تعمل الإشارات مع تدفق البيانات أحادي الاتجاه مثل React؟
ج : نعم ، الإشارات هي آلية لتدفق البيانات أحادي الاتجاه. تتيح لك أطر واجهة المستخدم المستندة إلى الإشارة التعبير عن وجهة نظرك كدالة للنموذج (حيث يتضمن النموذج إشارات). رسم بياني للدولة والإشارات المحسوبة هو acyclic عن طريق البناء. من الممكن أيضًا إعادة إنشاء antepatterns React داخل الإشارات (!) ، على سبيل المثال ، ما يعادل الإشارة من setState
داخل useEffect
هو استخدام مراقب لجدولة الكتابة إلى إشارة الحالة.
س : كيف ترتبط الإشارات بأنظمة إدارة الدولة مثل Redux؟ هل تشجع الإشارات حالة غير منظمة؟
ج : يمكن أن تشكل الإشارات أساسًا فعالًا لتجريد إدارة الدولة الشبيه بالمتجر. النمط الشائع الموجود في أطر عمل متعددة هو كائن يعتمد على وكيل يمثل خصائص داخليًا باستخدام الإشارات أو على سبيل المثال أو Vue reactive()
أو المتاجر الصلبة. تتيح هذه الأنظمة تجميع الحالة المرنة في المستوى الصحيح من التجريد للتطبيق المعين.
س : ما هي الإشارات التي تقدم هذا Proxy
لا يتعامل معه حاليًا؟
ج : الوكلاء والإشارات مكملة وتذهب بشكل جيد معًا. يتيح لك الوكلاء اعتراض عمليات الكائنات الضحلة والإشارات تنسيق رسم بياني تبعية (للخلايا). يعد دعم وكيل مع الإشارات وسيلة رائعة لصنع بنية تفاعلية متداخلة مع بيئة عمل كبيرة.
في هذا المثال ، يمكننا استخدام وكيل لجعل الإشارة لها خاصية getter و setter بدلاً من استخدام طرق get
and set
:
const a = new signal.state (0) ؛ const b = New Proxy (a ، { احصل على (الهدف ، الخاصية ، المتلقي) {if (property === 'value') {return target.get ():} } SET (الهدف ، الخاصية ، القيمة ، المتلقي) {if (property === 'value') {target.set (value)!} }}) ؛ // الاستخدام في سياق تفاعلي افتراضي: <قالب> {B.Value} <button onClick = {() => {B.Value ++ ؛ }}> تغيير </button> </mapplate>
عند استخدام العارض الذي تم تحسينه للتفاعل الدقيق ، سيؤدي النقر فوق الزر إلى تحديث خلية b.value
.
يرى:
أمثلة على الهياكل التفاعلية المتداخلة التي تم إنشاؤها مع كل من الإشارات والوكلاء: الإشارة الإشارات
مثال على التطبيقات السابقة التي توضح العلاقة بين الوكلاء ATD البيانات التفاعلية:
مناقشة.
س : هل الإشارات تعتمد على الدفع أو القائمة على السحب؟
ج : تقييم الإشارات المحسوبة يعتمد على السحب: يتم تقييم الإشارات المحسوبة فقط عند استدعاء .get()
، حتى لو تغيرت الحالة الأساسية قبل ذلك بكثير. في الوقت نفسه ، قد يؤدي تغيير إشارة الحالة على الفور إلى استدعاء مراقب ، "دفع" الإخطار. لذلك يمكن اعتبار الإشارات بناء "دفع".
س : هل تعرض الإشارات nondeterminism في تنفيذ JavaScript؟
ج : لا. بالنسبة لأحدهم ، فإن جميع عمليات الإشارة لها دلالات وترتيب محددة جيدًا ، ولن تختلف بين التطبيقات المطابقة. على مستوى أعلى ، تتبع الإشارات مجموعة معينة من الثنائيات ، فيما يتعلق بـ "صوت". تلاحظ الإشارة المحسوبة دائمًا الرسم البياني للإشارة في حالة متسقة ، ولا ينقطع تنفيذها عن طريق رمز تقويم الإشارات الآخر (باستثناء الأشياء التي تسميها نفسها). انظر الوصف أعلاه.
س : عندما أكتب إلى إشارة الحالة ، متى يتم تحديث الإشارة المحسوبة؟
ج : لم يتم جدولة! ستعمل الإشارة المحسوبة على إعادة حساب نفسها في المرة القادمة التي يقرأها شخص ما. بشكل متزامن ، قد يتم استدعاء رد الاتصال على مراقب notify
، مما يمكّن الأطر من تحديد موعد للقراءة في الوقت الذي يجدونه مناسبًا.
س : متى تدخل تكتب إشارات الدولة سارية المفعول؟ على الفور ، أم أنها متجانسة؟
ج : تنعكس كتابة إشارات الحالة على الفور-في المرة القادمة التي يتم فيها قراءة إشارة محسوبة تعتمد على إشارة الحالة ، ستعيد حساب نفسها إذا لزم الأمر ، حتى لو كانت في السطر التالي مباشرة من الكود. ومع ذلك ، فإن الكسل المتأصل في هذه الآلية (أن الإشارات المحسوبة يتم حسابها فقط عند القراءة) تعني أنه ، في الممارسة العملية ، قد تحدث الحسابات بطريقة مجزأة.
س : ما الذي يعنيه الإشارات لتمكين التنفيذ "الخالي من الخلل"؟
ج : واجهت النماذج القائمة في وقت سابق للتفاعل مسألة حساب زائدة عن الحاجة: إذا تسبب التحديث لإشارة الحالة في تشغيل الإشارة المحسوبة بفارغ الصبر ، فقد يؤدي ذلك في النهاية إلى دفع تحديث إلى واجهة المستخدم. لكن هذه الكتابة إلى واجهة المستخدم قد تكون سابقًا لأوانها ، إذا كان هناك تغيير آخر في إشارة الحالة الأصلية قبل الإطار التالي. في بعض الأحيان ، تم عرض القيم الوسيطة غير الدقيقة للمستخدمين النهائيين بسبب مثل هذه الخلافات. الإشارات تتجنب هذه الديناميكية من خلال أن تكون قائمة على السحب ، بدلاً من القائمة على الدفع: في الوقت الذي يحدد فيه الإطار عرض واجهة المستخدم ، فإنه سيسحب التحديثات المناسبة ، وتجنب العمل الضائع في الحساب وكذلك في الكتابة إلى DOM.
س : ماذا يعني أن تكون الإشارات "ضيلة"؟
ج : هذا هو الجانب الآخر للتنفيذ الخالي من الخلل: تمثل الإشارات خلية من البيانات-فقط القيمة الحالية الفورية (التي قد تتغير) ، وليس دفق البيانات مع مرور الوقت. لذلك ، إذا كتبت إلى إشارة حالة مرتين على التوالي ، دون القيام بأي شيء آخر ، فإن الكتابة الأولى "ضائعة" ولم ترها أي إشارات أو آثار محسوبة. من المفهوم أن يكون هذا ميزة بدلاً من وجود خطأ-بنيات أخرى (على سبيل المثال ، Async Teerables ، Observables) هي أكثر ملاءمة للتيارات.
س : هل ستكون الإشارات الأصلية أسرع من تطبيقات إشارة JS الموجودة؟
ج : نأمل ذلك (من خلال عامل ثابت صغير) ، ولكن هذا يبقى مثبت في الكود. محركات JS ليست سحرية ، وستحتاج في النهاية إلى تنفيذ نفس أنواع الخوارزميات مثل تطبيقات JS للإشارات. انظر القسم أعلاه حول الأداء.
س : لماذا لا يتضمن هذا الاقتراح وظيفة effect()
، عندما تكون التأثيرات ضرورية لأي استخدام عملي للإشارات؟
ج : اربط التأثيرات بطبيعتها في الجدولة والتخلص ، والتي تديرها الأطر وخارج نطاق هذا الاقتراح. بدلاً من ذلك ، يتضمن هذا الاقتراح الأساس لتنفيذ التأثيرات من خلال Signal.subtle.Watcher
ذات المستوى المنخفض.
س : لماذا الاشتراكات تلقائية بدلاً من توفير واجهة يدوية؟
ج : أظهرت التجربة أن واجهات الاشتراك اليدوي للتفاعل غير وحيوية معرضة للخطأ. التتبع التلقائي أكثر قابلية للتأليف وهو ميزة أساسية للإشارات.
س : لماذا يتم تشغيل رد اتصال Watcher
بشكل متزامن ، بدلاً من جدولة في مهندسة دقيقة؟
ج : نظرًا لأن رد الاتصال لا يمكن قراءة أو كتابة إشارات ، فلا يوجد أي عجب يثيره من خلال تسميته بشكل متزامن. سيؤدي رد الاتصال النموذجي إلى إضافة إشارة إلى صفيف ليتم قراءته لاحقًا ، أو وضع علامة في مكان ما. إنه أمر غير ضروري ومكلف بشكل غير عملي إنشاء مهام دقيقة منفصلة لجميع هذه الأنواع من الإجراءات.
س : تفتقد واجهة برمجة التطبيقات هذه بعض الأشياء اللطيفة التي يوفرها إطاري المفضل ، مما يجعل البرمجة مع الإشارات أسهل. هل يمكن إضافة ذلك إلى المعيار أيضًا؟
ج : ربما. لا تزال الامتدادات المختلفة قيد الدراسة. يرجى تقديم مشكلة لرفع مناقشة على أي ميزة مفقودة تجدها مهمة.
س : هل يمكن تخفيض API في الحجم أو التعقيد؟
ج : إنه بالتأكيد هدف لإبقاء واجهة برمجة التطبيقات هذه ضئيلًا ، وقد حاولنا القيام بذلك مع ما تم تقديمه أعلاه. إذا كان لديك أفكار لمزيد من الأشياء التي يمكن إزالتها ، فيرجى تقديم مشكلة لمناقشة.
س : ألا يجب أن نبدأ العمل في هذا المجال بمفهوم أكثر بدائية ، مثل Observables؟
ج : قد تكون الملاحظات فكرة جيدة لبعض الأشياء ، لكنها لا تحل المشكلات التي تهدف إلى حل المشكلات التي تهدف إلى حلها. كما هو موضح أعلاه ، فإن آليات النشر/الاشتراك الأخرى ليست حلاً كاملًا لأنواع العديد من برامج واجهة المستخدم ، نظرًا لعمل الكثير من أعمال التكوين المعرضة للخطأ للمطورين ، وتضيع العمل بسبب نقص الكسل ، من بين قضايا أخرى.
س : لماذا يتم اقتراح الإشارات في TC39 بدلاً من DOM ، بالنظر إلى أن معظم تطبيقاتها تعتمد على الويب؟
ج : يهتم بعض المؤلفين من هذا الاقتراح ببيئات واجهة المستخدم غير وايب كهدف ، ولكن في هذه الأيام ، قد يكون أي مكان مناسبًا لذلك ، حيث يتم تنفيذ واجهات برمجة التطبيقات على شبكة الإنترنت خارج الويب. في النهاية ، لا تحتاج الإشارات إلى الاعتماد على أي واجهات برمجة تطبيقات DOM ، لذلك يعمل في كلتا الحالتين. إذا كان لدى شخص ما سبب قوي لهذا المجموعة للتبديل ، فيرجى إخبارنا في مسألة ما. في الوقت الحالي ، وقع جميع المساهمين اتفاقيات الملكية الفكرية TC39 ، والخطة هي تقديم هذا إلى TC39.
س : ما هي المدة التي ستستغرقها حتى أتمكن من استخدام الإشارات القياسية؟
ج : تتوفر Polyfill بالفعل ، ولكن من الأفضل عدم الاعتماد على استقرارها ، حيث تتطور واجهة برمجة التطبيقات هذه أثناء عملية المراجعة. في غضون أشهر أو عام ، يجب أن يكون متعددة الجودة العالية وعالية الأداء قابلة للاستخدام ، ولكن هذا سيظل خاضعًا لمراجعات اللجنة وليس معتادًا بعد. في أعقاب المسار النموذجي لمقترح TC39 ، من المتوقع أن يستغرق ما لا يقل عن 2-3 سنوات في الحد الأدنى المطلق لإشارات أن تكون متوفرة أصلاً في جميع المتصفحات التي تعود إلى بعض الإصدارات ، بحيث لا تكون هناك حاجة إلى Polyfills.
س : كيف سنمنع توحيد النوع الخاطئ من الإشارات في وقت مبكر جدًا ، تمامًا مثل ميزة {js/web التي لا تعجبك}}؟
ج : إن مؤلفي هذا الاقتراح يخططون للذهاب إلى النماذج الإضافية مع النماذج الأولية وإثبات الأشياء قبل طلب التقدم المرحلة في TC39. انظر "خطة الحالة والتنمية" أعلاه. إذا رأيت فجوات في هذه الخطة أو فرص للتحسين ، فيرجى تقديم مشكلة تشرح.