التفاعلية عند الطلب في Vue 3

نشرت: 2022-03-11

بصرف النظر عن تحسينات الأداء الرائعة ، جلب Vue 3 الذي تم إصداره مؤخرًا العديد من الميزات الجديدة. يمكن القول إن أهم مقدمة هي Composition API . في الجزء الأول من هذه المقالة ، نلخص الدافع القياسي لواجهة برمجة تطبيقات جديدة: تنظيم أفضل للكود وإعادة الاستخدام. في الجزء الثاني ، سنركز على الجوانب الأقل نقاشًا لاستخدام واجهة برمجة التطبيقات الجديدة ، مثل تنفيذ الميزات القائمة على التفاعل والتي لا يمكن وصفها في نظام التفاعل الخاص بـ Vue 2.

سوف نشير إلى هذا على أنه تفاعل عند الطلب . بعد تقديم الميزات الجديدة ذات الصلة ، سنقوم ببناء تطبيق جداول بيانات بسيط لإظهار التعبير الجديد لنظام تفاعل Vue. في النهاية ، سنناقش ما قد يستخدمه العالم الحقيقي لهذا التحسين عند التفاعل عند الطلب.

ما الجديد في Vue 3 ولماذا هو مهم

Vue 3 هي إعادة كتابة رئيسية لـ Vue 2 ، حيث تقدم عددًا كبيرًا من التحسينات مع الاحتفاظ بالتوافق مع الإصدارات السابقة مع API القديم بالكامل تقريبًا.

واحدة من أهم الميزات الجديدة في Vue 3 هي Composition API . أثار تقديمه الكثير من الجدل عندما نوقش لأول مرة علنًا. في حال لم تكن على دراية بواجهة برمجة التطبيقات الجديدة بالفعل ، فسنصف أولاً الدافع وراءها.

الوحدة المعتادة لتنظيم الكود هي كائن JavaScript تمثل مفاتيحه أنواعًا مختلفة ممكنة من جزء من المكون. وبالتالي قد يحتوي الكائن على قسم واحد للبيانات التفاعلية ( data ) ، وقسم آخر للخصائص المحسوبة ( computed ) ، وقسم آخر لطرق المكونات ( methods ) ، إلخ.

بموجب هذا النموذج ، يمكن أن يكون للمكون وظائف متعددة غير مرتبطة أو غير مرتبطة بشكل وثيق يتم توزيع أعمالها الداخلية بين أقسام المكونات المذكورة أعلاه. على سبيل المثال ، قد يكون لدينا مكون لتحميل الملفات يقوم بتنفيذ وظيفتين منفصلتين بشكل أساسي: إدارة الملفات ونظام يتحكم في الرسوم المتحركة لحالة التحميل.

قد يحتوي جزء <script> على شيء مشابه لما يلي:

 export default { data () { return { animation_state: 'playing', animation_duration: 10, upload_filenames: [], upload_params: { target_directory: 'media', visibility: 'private', } } }, computed: { long_animation () { return this.animation_duration > 5; }, upload_requested () { return this.upload_filenames.length > 0; }, }, ... }

هناك فوائد لهذا النهج التقليدي لتنظيم الكود ، خاصة في عدم اضطرار المطور للقلق بشأن مكان كتابة جزء جديد من التعليمات البرمجية. إذا أضفنا متغيرًا تفاعليًا ، فسنقوم بإدخاله في قسم data . إذا كنا نبحث عن متغير موجود ، فنحن نعلم أنه يجب أن يكون في قسم data .

هذا النهج التقليدي لتقسيم تنفيذ الوظيفة إلى أقسام ( data ، computed ، إلخ) غير مناسب في جميع المواقف.

كثيرًا ما يتم الاستشهاد بالاستثناءات التالية:

  1. التعامل مع مكون بعدد كبير من الوظائف. إذا أردنا ترقية رمز الرسوم المتحركة الخاص بنا مع القدرة على تأخير بدء الرسوم المتحركة ، على سبيل المثال ، فسيتعين علينا التمرير / القفز بين جميع الأقسام ذات الصلة للمكون في محرر الكود. في حالة مكون تحميل الملفات الخاص بنا ، يكون المكون نفسه صغيرًا وعدد الوظائف التي ينفذها صغير أيضًا. وبالتالي ، في هذه الحالة ، لا يمثل القفز بين الأقسام مشكلة حقًا. تصبح مسألة تجزئة الكود ذات صلة عندما نتعامل مع المكونات الكبيرة.
  2. هناك حالة أخرى تفتقر إلى النهج التقليدي وهي إعادة استخدام الكود. غالبًا ما نحتاج إلى إنشاء مجموعة محددة من البيانات التفاعلية ، والخصائص المحسوبة ، والطرق ، وما إلى ذلك ، المتوفرة في أكثر من مكون واحد.

يقدم Vue 2 (و Vue 3 المتوافق مع الإصدارات السابقة) حلاً لمعظم مشكلات تنظيم الكود وإعادة الاستخدام: mixins .

إيجابيات وسلبيات Mixins في Vue 3

تسمح Mixins باستخراج وظائف المكون في وحدة منفصلة من التعليمات البرمجية. يتم وضع كل وظيفة في مزيج منفصل ويمكن لكل مكون استخدام مزيج واحد أو أكثر. يمكن استخدام القطع المحددة في mixin في أحد المكونات كما لو كانت محددة في المكون نفسه. تشبه المزيجات إلى حد ما الفئات في اللغات الموجهة للكائنات من حيث أنها تجمع الشفرة المتعلقة بوظيفة معينة. مثل الفئات ، يمكن وراثة mixins (مستخدمة) في وحدات أخرى من التعليمات البرمجية.

ومع ذلك ، فإن الاستدلال باستخدام mixins هو أصعب لأنه ، على عكس الفئات ، لا يلزم تصميم mixins مع وضع التغليف في الاعتبار. يُسمح لـ Mixins بأن تكون مجموعات من أجزاء التعليمات البرمجية غير المحكومة دون واجهة محددة جيدًا للعالم الخارجي. قد يؤدي استخدام أكثر من mixin في نفس الوقت في نفس المكون إلى وجود مكون يصعب فهمه واستخدامه.

معظم اللغات الموجهة للكائنات (على سبيل المثال ، C # و Java) تثبط أو لا تسمح بالميراث المتعدد على الرغم من حقيقة أن نموذج البرمجة الشيئية لديه الأدوات للتعامل مع مثل هذا التعقيد. (تسمح بعض اللغات بالميراث المتعدد ، مثل C ++ ، لكن التكوين لا يزال مفضلًا على الوراثة.)

هناك مشكلة أكثر عملية قد تحدث عند استخدام mixins في Vue وهي تضارب الأسماء ، والتي تحدث عند استخدام اثنين أو أكثر من mixins للإعلان عن الأسماء الشائعة. وتجدر الإشارة هنا إلى أنه إذا كانت استراتيجية Vue الافتراضية للتعامل مع تضارب الأسماء ليست مثالية في موقف معين ، فيمكن للمطور تعديل الاستراتيجية ، وهذا يأتي على حساب إدخال المزيد من التعقيد.

هناك مشكلة أخرى وهي أن mixins لا تقدم شيئًا مشابهًا لمنشئ الفصل. هذه مشكلة لأنه غالبًا ما نحتاج إلى وظائف متشابهة جدًا ، ولكنها ليست متطابقة تمامًا ، لتكون موجودة في مكونات مختلفة. يمكن التحايل على هذا في بعض الحالات البسيطة باستخدام مصانع mixin.

لذلك ، لا تعد mixins الحل الأمثل لتنظيم الكود وإعادة استخدامه ، وكلما زاد حجم المشروع ، أصبحت مشكلاتهم أكثر خطورة. يقدم Vue 3 طريقة جديدة لحل نفس المشكلات المتعلقة بتنظيم الكود وإعادة استخدامه.

تكوين API: رد Vue 3 على تنظيم الكود وإعادة استخدامه

تسمح لنا واجهة برمجة تطبيقات التركيب (ولكنها لا تتطلب منا) بفصل أجزاء المكون تمامًا. يمكن تعريف كل جزء من الكود - متغير أو خاصية محسوبة أو ساعة أو ما إلى ذلك - بشكل مستقل.

على سبيل المثال ، بدلاً من وجود كائن يحتوي على قسم data يحتوي على مفتاح animation_state مع القيمة (الافتراضية) "تشغيل" ، يمكننا الآن كتابة (في أي مكان في كود JavaScript الخاص بنا):

 const animation_state = ref('playing');

التأثير يكاد يكون مماثلاً للإعلان عن هذا المتغير في قسم data لبعض المكونات. الاختلاف الأساسي الوحيد هو أننا نحتاج إلى جعل ref معرّفًا خارج المكون متاحًا في المكون حيث ننوي استخدامه. نقوم بذلك عن طريق استيراد الوحدة النمطية الخاصة به إلى المكان الذي يتم فيه تعريف المكون وإرجاع ref من قسم setup للمكون. سنتخطى هذا الإجراء في الوقت الحالي ونركز فقط على واجهة برمجة التطبيقات الجديدة للحظة. لا تتطلب التفاعلية في Vue 3 مكونًا ؛ إنه في الواقع نظام قائم بذاته.

يمكننا استخدام المتغير animation_state في أي نطاق نقوم باستيراد هذا المتغير إليه. بعد إنشاء ref ، نحصل على قيمته الفعلية ونضبطها باستخدام ref.value ، على سبيل المثال:

 animation_state.value = 'paused'; console.log(animation_state.value);

نحن بحاجة إلى لاحقة ".value" لأن عامل التخصيص سيعين بخلاف ذلك القيمة (غير التفاعلية) "متوقفة مؤقتًا" إلى animation_state المتغير. تتطلب التفاعلية في جافا سكريبت (سواء عندما يتم تنفيذها من خلال defineProperty كما في Vue 2 ، وعندما تكون قائمة على Proxy كما في Vue 3) تتطلب كائنًا يمكننا العمل بمفاتيحه بشكل تفاعلي.

لاحظ أن هذا هو الحال في Vue 2 أيضًا ؛ هناك ، كان لدينا مكون كبادئة لأي عضو بيانات تفاعلي ( component.data_member ). ما لم تقدم لغة JavaScript القياسية القدرة على زيادة تحميل عامل التخصيص ، ستتطلب التعبيرات التفاعلية كائنًا ومفتاحًا (على سبيل المثال ، animation_state value كما هو مذكور أعلاه) لتظهر على الجانب الأيسر من أي عملية تعيين حيث نرغب في ذلك الحفاظ على التفاعل.

في القوالب ، يمكننا حذف .value حيث يتعين على Vue معالجة رمز القالب مسبقًا ويمكنه اكتشاف المراجع تلقائيًا:

 <animation :state='animation_state' />

من الناحية النظرية ، يمكن لمجمع Vue معالجة جزء <script> من مكون ملف واحد (SFC) بطريقة مماثلة أيضًا ، بإدخال .value عند الحاجة. ومع ذلك ، قد يختلف استخدام المراجع بناءً على ما إذا كنا نستخدم refs أم لا ، لذلك ربما تكون هذه الميزة غير مرغوب فيها.

في بعض الأحيان ، لدينا كيان (على سبيل المثال ، يكون كائنًا أو مصفوفة جافا سكريبت) لا ننوي أبدًا استبداله بمثيل مختلف تمامًا. بدلاً من ذلك ، قد نكون مهتمين فقط بتعديل الحقول ذات المفاتيح. يوجد اختصار في هذه الحالة: استخدام reactive بدلاً من ref يسمح لنا .value عن القيمة.

 const upload_params = reactive({ target_directory: 'media', visibility: 'private', }); upload_params.visibility = 'public'; // no `.value` needed here // if we did not make `upload_params` constant, the following code would compile but we would lose reactivity after the assignment; it is thus a good idea to make reactive variables ```const``` explicitly: upload_params = { target_directory: 'static', visibility: 'public', };

التفاعل المنفصل مع ref reactive ليست ميزة جديدة تمامًا لـ Vue 3. تم تقديمها جزئيًا في Vue 2.6 ، حيث كانت تسمى مثل هذه الحالات المنفصلة من البيانات التفاعلية "الملاحظات". بالنسبة للجزء الأكبر ، يمكن للمرء أن يحل محل Vue.observable مع reactive . يتمثل أحد الاختلافات في أن الوصول إلى الكائن الذي تم تمريره إلى Vue.observable مباشرة أمر تفاعلي ، بينما تقوم واجهة برمجة التطبيقات الجديدة بإرجاع كائن وكيل ، لذلك لن يكون لتغيير الكائن الأصلي تأثيرات تفاعلية.

المقارنة: Options API مقابل Composition API.

الجديد تمامًا في Vue 3 هو أنه يمكن الآن تعريف الأجزاء التفاعلية الأخرى للمكون بشكل مستقل أيضًا ، بالإضافة إلى البيانات التفاعلية. يتم تنفيذ الخصائص المحسوبة بطريقة متوقعة:

 const x = ref(5); const x_squared = computed(() => x.value * x.value); console.log(x_squared.value); // outputs 25

وبالمثل يمكن تنفيذ أنواع مختلفة من الساعات وطرق دورة الحياة وحقن التبعية. من أجل الإيجاز ، لن نغطي هؤلاء هنا.

لنفترض أننا نستخدم نهج SFC القياسي لتطوير Vue. قد نستخدم أيضًا واجهة برمجة التطبيقات التقليدية ، مع أقسام منفصلة للبيانات ، والخصائص المحسوبة ، وما إلى ذلك. كيف يمكننا دمج أجزاء تفاعلية صغيرة من Composition API مع SFCs؟ يقدم Vue 3 قسمًا آخر لهذا فقط: setup . يمكن اعتبار القسم الجديد طريقة دورة حياة جديدة (يتم تنفيذها قبل أي خطاف آخر - على وجه الخصوص ، قبل created ).

فيما يلي مثال لمكون كامل يدمج النهج التقليدي مع Composition API:

 <template> <input v-model="x" /> <div>Squared: {{ x_squared }}, negative: {{ x_negative }}</div> </template> <script> import { ref, computed } from 'vue'; export default { name: "Demo", computed: { x_negative() { return -this.x; } }, setup() { const x = ref(0); const x_squared = computed(() => x.value * x.value); return {x, x_squared}; } } </script>

أشياء يجب استبعادها من هذا المثال:

  • يتم الآن setup جميع أكواد Composition API Code. قد ترغب في إنشاء ملف منفصل لكل وظيفة ، واستيراد هذا الملف في SFC ، وإرجاع وحدات التفاعل المطلوبة من setup (لإتاحتها لبقية المكون).
  • يمكنك مزج الأسلوب الجديد والتقليدي في نفس الملف. لاحظ أن x ، على الرغم من كونه مرجعًا ، لا يتطلب .value عند الإشارة إليه في رمز القالب أو في الأقسام التقليدية لمكون مثل computed .
  • أخيرًا وليس آخرًا ، لاحظ أن لدينا عقدتين جذريتين في نموذج DOM ؛ تعد القدرة على الحصول على عقد جذر متعددة ميزة جديدة أخرى لـ Vue 3.

التفاعلية هي أكثر تعبيرا في Vue 3

في الجزء الأول من هذه المقالة ، تطرقنا إلى الدافع القياسي لـ Composition API ، وهو تنظيم الكود المحسن وإعادة الاستخدام. في الواقع ، لا تكمن نقطة البيع الرئيسية لواجهة برمجة التطبيقات الجديدة في قوتها ، بل في الراحة التنظيمية التي توفرها: القدرة على هيكلة الكود بشكل أكثر وضوحًا. قد يبدو أن هذا هو كل شيء - أن Composition API يتيح طريقة لتنفيذ المكونات التي تتجنب قيود الحلول الموجودة بالفعل ، مثل mixins.

ومع ذلك ، هناك المزيد لواجهة برمجة التطبيقات الجديدة. في الواقع ، لا تتيح واجهة برمجة التطبيقات (API) الخاصة بالتركيب أنظمة تفاعلية أفضل تنظيماً فحسب ، بل أنظمة تفاعلية أكثر قوة. المكون الرئيسي هو القدرة على إضافة تفاعل ديناميكي للتطبيق. في السابق ، كان على المرء تحديد جميع البيانات ، وجميع الخصائص المحسوبة ، وما إلى ذلك قبل تحميل أحد المكونات. لماذا قد تكون إضافة كائنات تفاعلية في مرحلة لاحقة مفيدة؟ فيما تبقى نلقي نظرة على مثال أكثر تعقيدًا: جداول البيانات.

إنشاء جدول بيانات في Vue 2

تحتوي أدوات جداول البيانات مثل Microsoft Excel و LibreOffice Calc و Google Sheets على نوع من نظام التفاعل. تقدم هذه الأدوات للمستخدم جدولاً ، مع أعمدة مفهرسة من قبل A – Z ، AA – ZZ ، AAA – ZZZ ، إلخ ، وصفوف مفهرسة رقميًا.

قد تحتوي كل خلية على قيمة عادية أو صيغة. الخلية التي تحتوي على صيغة هي في الأساس خاصية محسوبة ، والتي قد تعتمد على القيم أو الخصائص المحسوبة الأخرى. باستخدام جداول البيانات القياسية (وعلى عكس نظام التفاعل في Vue) ، يُسمح لهذه الخصائص المحسوبة بالاعتماد على نفسها! هذا المرجع الذاتي مفيد في بعض السيناريوهات حيث يتم الحصول على القيمة المرغوبة عن طريق التقريب التكراري.

بمجرد تغيير محتوى الخلية ، ستقوم جميع الخلايا التي تعتمد على الخلية المعنية بتشغيل تحديث. في حالة حدوث تغييرات أخرى ، قد تتم جدولة مزيد من التحديثات.

إذا أردنا إنشاء تطبيق جداول بيانات باستخدام Vue ، فسيكون من الطبيعي أن نسأل عما إذا كان بإمكاننا استخدام نظام التفاعل الخاص بـ Vue وجعل Vue محرك تطبيق جداول البيانات. لكل خلية ، يمكننا تذكر قيمتها الخام القابلة للتحرير ، وكذلك القيمة المحسوبة المقابلة. تعكس القيم المحسوبة القيمة الأولية إذا كانت قيمة عادية ، وبخلاف ذلك ، تكون القيم المحسوبة نتيجة التعبير (الصيغة) الذي تمت كتابته بدلاً من القيمة العادية.

باستخدام Vue 2 ، تتمثل إحدى طرق تنفيذ جدول بيانات في الحصول على raw_values صفيف ثنائية الأبعاد من السلاسل ، وقيم computed_values (محسوبة) ثنائية الأبعاد لمصفوفة قيم الخلية.

إذا كان عدد الخلايا صغيرًا وثابتًا قبل تحميل مكون Vue المناسب ، فيمكننا الحصول على قيمة خام واحدة وقيمة محسوبة واحدة لكل خلية في الجدول في تعريف المكون الخاص بنا. بصرف النظر عن الوحشية الجمالية التي قد يسببها مثل هذا التنفيذ ، فإن الجدول الذي يحتوي على عدد ثابت من الخلايا في وقت الترجمة ربما لا يعتبر جدول بيانات.

كما توجد مشاكل في المصفوفة ثنائية الأبعاد computed_values . تكون الخاصية المحسوبة دائمًا دالة يعتمد تقييمها ، في هذه الحالة ، على نفسها (يتطلب حساب قيمة الخلية ، بشكل عام ، حساب بعض القيم الأخرى بالفعل). حتى إذا سمحت Vue بالخصائص المحسوبة ذاتي المرجع ، فإن تحديث خلية واحدة سيؤدي إلى إعادة حساب جميع الخلايا (بغض النظر عما إذا كانت هناك تبعيات أم لا). سيكون هذا غير فعال للغاية. وبالتالي ، قد ينتهي بنا الأمر باستخدام التفاعلية لاكتشاف التغييرات في البيانات الأولية باستخدام Vue 2 ، ولكن يجب تنفيذ كل شيء آخر من ناحية التفاعل من نقطة الصفر.

نمذجة القيم المحسوبة في Vue 3

باستخدام Vue 3 ، يمكننا تقديم خاصية محسوبة جديدة لكل خلية. في حالة نمو الجدول ، يتم تقديم خصائص محسوبة جديدة.

لنفترض أن لدينا الخلايا A1 و A2 ، ونرغب في أن تعرض A2 مربع A1 الذي تكون قيمته هي الرقم 5. رسم تخطيطي لهذا الموقف:

 let A1 = computed(() => 5); let A2 = computed(() => A1.value * A1.value); console.log(A2.value); // outputs 25

لنفترض أننا بقينا في هذا السيناريو البسيط للحظة. هناك مشكلة هنا. ماذا لو أردنا تغيير A1 بحيث يحتوي على الرقم 6؟ لنفترض أننا نكتب هذا:

 A1 = computed(() => 6); console.log(A2.value); // outputs 25 if we already ran the code above

هذا لم يغير فقط القيمة من 5 إلى 6 في A1 . المتغير A1 له هوية مختلفة تمامًا الآن: الخاصية المحسوبة التي تتحول إلى الرقم 6. ومع ذلك ، لا يزال المتغير A2 يتفاعل مع التغييرات في الهوية القديمة للمتغير A1 . لذلك ، لا ينبغي أن يشير A2 إلى A1 مباشرةً ، بل إلى كائن خاص سيكون متاحًا دائمًا في السياق ، وسيخبرنا بما هو A1 في الوقت الحالي. بعبارة أخرى ، نحتاج إلى مستوى من المراوغة قبل الوصول إلى A1 ، مثل المؤشر. لا توجد مؤشرات ككيانات من الدرجة الأولى في جافا سكريبت ، ولكن من السهل محاكاة أحدها. إذا كنا نرغب في الحصول على pointer يشير إلى value ، فيمكننا إنشاء pointer = {points_to: value} . إعادة توجيه مقادير المؤشر إلى التعيين إلى pointer.points_to ، وإلغاء الإشارة (الوصول إلى القيمة المشار إليها) يؤدي إلى استرداد قيمة pointer.points_to . في حالتنا نقوم بما يلي:

 let A1 = reactive({points_to: computed(() => 5)}); let A2 = reactive({points_to: computed(() => A1.points_to * A1.points_to)}); console.log(A2.points_to); // outputs 25

يمكننا الآن التعويض بـ 5 بـ 6.

 A1.points_to = computed(() => 6); console.log(A2.points_to); // outputs 36

على خادم Discord الخاص بـ Vue ، اقترح المستخدم redblobgames طريقة أخرى مثيرة للاهتمام: بدلاً من استخدام القيم المحسوبة ، استخدم المراجع التي تغلف الوظائف العادية. بهذه الطريقة ، يمكن للمرء أن يتبادل بالمثل الوظيفة دون تغيير هوية المرجع نفسه.

سيحتوي تطبيق جدول البيانات الخاص بنا على خلايا يُشار إليها بمفاتيح بعض المصفوفات ثنائية الأبعاد. يمكن أن توفر هذه المجموعة مستوى المراوغة الذي نطلبه. وبالتالي في حالتنا ، لن نطلب أي محاكاة إضافية للمؤشر. يمكن أن يكون لدينا حتى مصفوفة واحدة لا تميز بين القيم الخام والمحسوبة. يمكن أن يكون كل شيء قيمة محسوبة:

 const cells = reactive([ computed(() => 5), computed(() => cells[0].value * cells[0].value) ]); cells[0] = computed(() => 6); console.log(cells[1].value); // outputs 36

ومع ذلك ، نريد حقًا التمييز بين القيم الأولية والمحسوبة لأننا نريد أن نكون قادرين على ربط القيمة الأولية بعنصر إدخال HTML. علاوة على ذلك ، إذا كان لدينا مصفوفة منفصلة للقيم الأولية ، فلن نضطر أبدًا إلى تغيير تعريفات الخصائص المحسوبة ؛ سيتم تحديثها تلقائيًا بناءً على البيانات الأولية.

تنفيذ جدول البيانات

لنبدأ ببعض التعريفات الأساسية ، والتي في معظمها تشرح نفسها بنفسها.

 const rows = ref(30), cols = ref(26); /* if a string codes a number, return the number, else return a string */ const as_number = raw_cell => /^[0-9]+(\.[0-9]+)?$/.test(raw_cell) ? Number.parseFloat(raw_cell) : raw_cell; const make_table = (val = '', _rows = rows.value, _cols = cols.value) => Array(_rows).fill(null).map(() => Array(_cols).fill(val)); const raw_values = reactive(make_table('', rows.value, cols.value)); const computed_values = reactive(make_table(undefined, rows.value, cols.value)); /* a useful metric for debugging: how many times did cell (re)computations occur? */ const calculations = ref(0);

الخطة لكل computed_values[row][column] ليتم حسابها على النحو التالي. إذا لم raw_values[row][column] بـ = ، فقم بإرجاع raw_values[row][column] . خلافًا لذلك ، قم بتحليل الصيغة ، وتجميعها في JavaScript ، وتقييم التعليمات البرمجية المترجمة ، وإرجاع القيمة. لإبقاء الأمور قصيرة ، سنخدع قليلاً في تحليل الصيغ ولن نقوم ببعض التحسينات الواضحة هنا ، مثل ذاكرة التخزين المؤقت للترجمة.

سنفترض أنه يمكن للمستخدمين إدخال أي تعبير JavaScript صالح كصيغة. يمكننا استبدال المراجع لأسماء الخلايا التي تظهر في تعبيرات المستخدم ، مثل A1 ، B5 ، وما إلى ذلك ، بالإشارة إلى قيمة الخلية الفعلية (المحسوبة). تقوم الوظيفة التالية بهذه المهمة ، على افتراض أن السلاسل التي تشبه أسماء الخلايا تحدد الخلايا دائمًا (وليست جزءًا من بعض تعبيرات JavaScript غير ذات الصلة). للتبسيط ، سنفترض أن مؤشرات الأعمدة تتكون من حرف واحد.

 const letters = Array(26).fill(0) .map((_, i) => String.fromCharCode("A".charCodeAt(0) + i)); const transpile = str => { let cell_replacer = (match, prepend, col, row) => { col = letters.indexOf(col); row = Number.parseInt(row) - 1; return prepend + ` computed_values[${row}][${col}].value `; }; return str.replace(/(^|[^AZ])([AZ])([0-9]+)/g, cell_replacer); };

باستخدام وظيفة transpile ، يمكننا الحصول على تعبيرات JavaScript نقية من التعبيرات المكتوبة في "الامتداد" الصغير لجافا سكريبت مع مراجع الخلايا.

الخطوة التالية هي إنشاء خصائص محسوبة لكل خلية. سيحدث هذا الإجراء مرة واحدة في عمر كل خلية. يمكننا إنشاء مصنع يعيد الخصائص المحسوبة المطلوبة:

 const computed_cell_generator = (i, j) => { const computed_cell = computed(() => { // we don't want Vue to think that the value of a computed_cell depends on the value of `calculations` nextTick(() => ++calculations.value); let raw_cell = raw_values[i][j].trim(); if (!raw_cell || raw_cell[0] != '=') return as_number(raw_cell); let user_code = raw_cell.substring(1); let code = transpile(user_code); try { // the constructor of a Function receives the body of a function as a string let fn = new Function(['computed_values'], `return ${code};`); return fn(computed_values); } catch (e) { return "ERROR"; } }); return computed_cell; }; for (let i = 0; i < rows.value; ++i) for (let j = 0; j < cols.value; ++j) computed_values[i][j] = computed_cell_generator(i, j);

إذا وضعنا كل الكود أعلاه في طريقة setup ، فسنحتاج إلى إرجاع {raw_values, computed_values, rows, cols, letters, calculations} .

أدناه ، نقدم المكون الكامل ، إلى جانب واجهة المستخدم الأساسية.

الكود متاح على GitHub ، ويمكنك أيضًا الاطلاع على العرض التوضيحي المباشر.

 <template> <div> <div>Calculations: {{ calculations }}</div> <table class="table" border="0"> <tr class="row"> <td></td> <td class="column" v-for="(_, j) in cols" :key="'header' + j" > {{ letters[j] }} </td> </tr> <tr class="row" v-for="(_, i) in rows" :key="i" > <td class="column"> {{ i + 1 }} </td> <td class="column" v-for="(__, j) in cols" :key="i + '-' + j" :class="{ column_selected: active(i, j), column_inactive: !active(i, j), }" @click="activate(i, j)" > <div v-if="active(i, j)"> <input :ref="'input' + i + '-' + j" v-model="raw_values[i][j]" @keydown.enter.prevent="ui_enter()" @keydown.esc="ui_esc()" /> </div> <div v-else v-html="computed_value_formatter(computed_values[i][j].value)"/> </td> </tr> </table> </div> </template> <script> import {ref, reactive, computed, watchEffect, toRefs, nextTick, onUpdated} from "vue"; export default { name: 'App', components: {}, data() { return { ui_editing_i: null, ui_editing_j: null, } }, methods: { get_dom_input(i, j) { return this.$refs['input' + i + '-' + j]; }, activate(i, j) { this.ui_editing_i = i; this.ui_editing_j = j; nextTick(() => this.get_dom_input(i, j).focus()); }, active(i, j) { return this.ui_editing_i === i && this.ui_editing_j === j; }, unselect() { this.ui_editing_i = null; this.ui_editing_j = null; }, computed_value_formatter(str) { if (str === undefined || str === null) return 'none'; return str; }, ui_enter() { if (this.ui_editing_i < this.rows - 1) this.activate(this.ui_editing_i + 1, this.ui_editing_j); else this.unselect(); }, ui_esc() { this.unselect(); }, }, setup() { /*** All the code we wrote above goes here. ***/ return {raw_values, computed_values, rows, cols, letters, calculations}; }, } </script> <style> .table { margin-left: auto; margin-right: auto; margin-top: 1ex; border-collapse: collapse; } .column { box-sizing: border-box; border: 1px lightgray solid; } .column:first-child { background: #f6f6f6; min-width: 3em; } .column:not(:first-child) { min-width: 4em; } .row:first-child { background: #f6f6f6; } #empty_first_cell { background: white; } .column_selected { border: 2px cornflowerblue solid !important; padding: 0px; } .column_selected input, .column_selected input:active, .column_selected input:focus { outline: none; border: none; } </style>

ماذا عن الاستخدام في العالم الحقيقي؟

لقد رأينا كيف أن نظام التفاعل المنفصل لـ Vue 3 لا يتيح فقط رمزًا أنظف ولكنه يسمح بأنظمة تفاعلية أكثر تعقيدًا استنادًا إلى آلية التفاعل الجديدة لـ Vue. لقد مرت سبع سنوات تقريبًا منذ تقديم Vue ، ومن الواضح أن الارتفاع في التعبير لم يكن مطلوبًا بشدة.

مثال جدول البيانات هو عرض توضيحي مباشر لما يمكن لـ Vue فعله الآن ، ويمكنك أيضًا الاطلاع على العرض التوضيحي المباشر.

ولكن كمثال حقيقي ، فهو مكان مناسب إلى حد ما. في أي نوع من المواقف قد يكون النظام الجديد مفيدًا؟ قد تكون حالة الاستخدام الأكثر وضوحًا للتفاعل عند الطلب هي مكاسب الأداء للتطبيقات المعقدة.

مقارنة قمع Vue 2 مقابل Vue 3.

في تطبيقات الواجهة الأمامية التي تعمل مع كمية كبيرة من البيانات ، قد يكون للحمل الزائد لاستخدام تفاعل ضعيف التفكير تأثير سلبي على الأداء. لنفترض أن لدينا تطبيق لوحة معلومات للأعمال ينتج تقارير تفاعلية عن نشاط أعمال الشركة. يمكن للمستخدم تحديد نطاق زمني وإضافة أو إزالة مؤشرات الأداء في التقرير. قد تعرض بعض المؤشرات قيمًا تعتمد على مؤشرات أخرى.

طريقة واحدة لتنفيذ إنشاء التقرير من خلال بنية متجانسة. عندما يغير المستخدم معلمة إدخال في الواجهة ، يتم تحديث خاصية واحدة محسوبة ، على سبيل المثال ، report_data . يتم حساب هذه الخاصية المحسوبة وفقًا لخطة مضمنة: أولاً ، احسب جميع مؤشرات الأداء المستقلة ، ثم تلك التي تعتمد فقط على هذه المؤشرات المستقلة ، إلخ.

سيؤدي التنفيذ الأفضل إلى فصل أجزاء من التقرير وحسابها بشكل مستقل. هناك بعض الفوائد لهذا:

  • لا يتعين على المطور كتابة ترميز ثابت لخطة التنفيذ ، والتي تكون مملة وعرضة للخطأ. سيكتشف نظام تفاعل Vue تلقائيًا التبعيات.
  • اعتمادًا على كمية البيانات المعنية ، قد نحصل على مكاسب كبيرة في الأداء لأننا نقوم فقط بتحديث بيانات التقرير التي تعتمد منطقيًا على معلمات الإدخال المعدلة.

إذا كانت جميع مؤشرات الأداء التي قد تكون جزءًا من التقرير النهائي معروفة قبل تحميل مكون Vue ، فقد نتمكن من تنفيذ الفصل المقترح حتى مع Vue 2. وإلا ، إذا كانت الواجهة الخلفية هي المصدر الوحيد للحقيقة (وهو عادةً ما يكون الحال مع التطبيقات المستندة إلى البيانات) ، أو إذا كان هناك موفرو بيانات خارجيون ، فيمكننا إنشاء خصائص محسوبة عند الطلب لكل جزء من التقرير.

بفضل Vue 3 ، أصبح هذا الآن ليس ممكنًا فحسب ، بل من السهل القيام به.