الأداء
نظرة عامة
صمم Vue ليكون فعالاً في حالات الاستخدام الأكثر شيوعًا دون الحاجة إلى إجراء تحسينات يدوية. ومع ذلك ، هناك دائمًا سيناريوهات صعبة تتطلب مزيدًا من الضبط الدقيق. في هذا القسم ، سنناقش ما يجب الانتباه إليه عندما يتعلق الأمر بالأداء في تطبيق Vue.
أولاً ، دعنا نناقش الجانبين الرئيسيين لأداء الويب:
أداء تحميل الصفحة: مدى سرعة عرض التطبيق للمحتوى ويصبح تفاعليًا في الزيارة الأولى. سيقاس ذلك عادةً باستخدام مقاييس الويب الحيوية مثل أكبر رسم مضمون (LCP) Largest Contentful Paint و أول تأخير في الإدخال (FID) First Input Delay.
تحديث الأداء: مدى سرعة تحديث التطبيق استجابةً لما يدخله المستخدم. على سبيل المثال ، مدى سرعة تحديث القائمة عندما يكتب المستخدم في مربع بحث ، أو مدى سرعة تبديل الصفحة عندما ينقر المستخدم على رابط في تطبيق أحادي الصفحة (SPA).
في حين أنه سيكون من المثالي تعظيم كليهما ، تميل هياكل الواجهة المختلفة إلى التأثير على مدى سهولة تحقيق الأداء المطلوب في هذه الجوانب. بالإضافة إلى ذلك ، يؤثر نوع التطبيق الذي تقوم ببنائه بشكل كبير على ما يجب أن تحدده من أولويات الأداء. لذلك ، فإن الخطوة الأولى لضمان الأداء الأمثل هي اختيار البنية المناسبة لنوع التطبيق الذي تقوم ببنائه:
اطلع على طرق استخدام Vue لترى كيف يمكنك الاستفادة من Vue بطرق مختلفة.
Jason Miller يناقش أنواع تطبيقات الويب والتنفيذ / التسليم المثالي لكل منها في التطبيقات النموذجية.
خيارات التحليل
لتحسين الأداء ، نحتاج أولاً إلى معرفة كيفية قياسه. هناك عدد من الأدوات الرائعة التي يمكن أن تساعد في هذا الصدد:
لتحليل أداء التحميل لعمليات نشر الإنتاج:
لتحليل الأداء أثناء التطوير المحلي:
- لوحة أداء أدوات التطوير المدمجة الخاصة بChrome
app.config.performance
يُمكّن علامات الأداء الخاصة بـ Vue في الجدول الزمني لأداء أدوات التطوير المدمجة الخاصة بChrome.
- Vue DevTools Extension يوفر أيضًا ميزة تحليل ملامح الأداء.
تحسينات تحميل الصفحة
هناك العديد من الجوانب الحيادية لإطار العمل لتحسين أداء تحميل الصفحة - راجع دليل web.dev هذا للحصول على تقرير شامل. هنا ، سنركز بشكل أساسي على التقنيات الخاصة بـ Vue.
اختيار المخطط الصحيح
إذا كانت حالة الاستخدام لديك حساسة لأداء تحميل الصفحة ، فتجنب شحنها على أنها تطبيق أحادي صفحة SPA خالص من جانب المستخدم. تريد أن يرسل الخادوم الخاص بك مباشرةً HTML يحتوي على المحتوى الذي يريد المستخدمون رؤيته. يعاني العرض الخالص من جانب المستخدم من بطء الوقت في الوصول إلى المحتوى. يمكن تخفيف ذلك باستخدام التصيير من الخادوم (SSR) أو توليد موقع ساكن (SSG). تحقق من دليل توجيه التصيير من الخادوم SSR لمعرفة المزيد حول أداء التصيير من الخادوم SSR باستخدام Vue. إذا لم يكن لتطبيقك متطلبات تفاعلية غنية ، فيمكنك أيضًا استخدام خادوم تقليدي لتصيير HTML وتحسينه باستخدام Vue على المستخدم.
إذا كان التطبيق الرئيسي الخاص بك يجب أن يكون تطبيق أحادي الصفحة SPA ، ولكن يحتوي على صفحات تسويقية (واجهة ، حول ، مدونة) ، اشحنها بشكل منفصل! يجب نشر صفحات التسويق الخاصة بك بشكل مثالي بتنسيق HTML ثابت مع الحد الأدنى من JS ، باستخدام توليد موقع ساكن SSG.
حجم الحزمة و عدم وجود شيفرات ميتة
من أكثر الطرق فعالية لتحسين أداء تحميل الصفحات هي شحن حزم JavaScript أصغر حجمًا. فيما يلي بعض الطرق لتقليل حجم الحزمة عند استخدام Vue:
استخدم عملية بناء إن أمكن.
العديد من واجهات Vue البرمجية تكون "بعدم وجود شيفرات ميتة" إذا تجمعت عبر أداة بناء حديثة. على سبيل المثال ، إذا لم تستخدم المكوّن المدمج
<Transition>
، فلن يُضمَّن في حزمة الإنتاج النهائية. يمكن أن يؤدي حذف الشيفرات الميتة أيضًا إلى إزالة الوحدات الأخرى غير المستخدمة في الشيفرة المصدرية الخاصة بك.عند استخدام عملية بناء ، تُصرف القوالب مسبقًا ، لذلك لا نحتاج إلى شحن مصرف Vue إلى المتصفح. هذا يوفر 14kb على الأقل + ملفات JavaScript مضغوطة ويتجنب تكلفة التصريف وقت التشغيل.
كن حذرًا من الحجم عند إدخال اعتماديات جديدة! في التطبيقات الواقعية ، غالبًا ما تكون الحزم المتضخمة نتيجة لإدخال إعتمادات ثقيلة دون إدراك ذلك.
إذا كنت تستخدم عملية بناء، ففضل الإعتماديات التي تقدم تنسيقات وحدات ES وتكون ملائمة لحذف الشيفرات الميتة. على سبيل المثال ، يفضل
lodash-es
علىlodash
.تحقق من حجم الإعتمادية وقيّم ما إذا كانت تستحق الوظيفة التي توفرها. لاحظ أنه إذا كانت الإعتمادية مناسبة لحذف الشيفرات الميتة ، فسيعتمد زيادة الحجم على الواجهات البرمجية التي تستوردها منها. يمكن استخدام أدوات مثل bundlejs.com لإجراء فحوصات سريعة ، ولكن القياس باستخدام إعداد البناء الفعلي سيكون دائمًا هو الأكثر دقة.
إذا كنت تستخدم Vue بشكل أساسي للتحسين التقدمي وتفضل تجنب عملية البناء ، ففكر في استخدام petite-vue (فقط 6kb) بدلاً من ذلك.
تقسيم الشيفرة
تقسيم الشيفرة هو المكان الذي تقوم فيه أداة البناء بتقسيم حزمة التطبيق إلى أجزاء متعددة أصغر ، والتي يمكن تحميلها عند الطلب أو بالتوازي. من خلال التقسيم المناسب للشيفرة ، يمكن تنزيل الميزات المطلوبة عند تحميل الصفحة على الفور ، مع تحميل أجزاء إضافية عند الحاجة فقط ، وبالتالي تحسين الأداء.
مجموعات مثل Rollup (التي يعتمد عليها Vite) أو webpack يمكنها إنشاء أجزاء مقسمة تلقائيًا عن طريق اكتشاف بنية الاستيراد الديناميكي لـ ESM:
js
// lazy.js و إعتماداتها سيفصلون إلى جزء منفصل
// وستحمل فقط عند استدعاء `loadLazy()`.
function loadLazy() {
return import('./lazy.js')
}
من الأفضل استخدام التحميل الخامل في الميزات غير المطلوبة فورًا بعد التحميل الأولي للصفحة. في تطبيقات Vue ، يمكن استخدام هذا مع ميزة Vue المكون الغير متزامن لإنشاء أجزاء مقسمة لأشجار المكونات:
js
import { defineAsyncComponent } from 'vue'
// سينشيء جزء منفصل لـ Foo.vue و إعتماداته.
// سيجلب عند الطلب فقط عند عرض
// المكون غير المتزامن على الصفحة.
const Foo = defineAsyncComponent(() => import('./Foo.vue'))
بالنسبة للتطبيقات التي تستخدم Vue Router ، يوصى بشدة باستخدام التحميل الخامل لمكونات التوجيه. يحتوي Vue Router على دعم صريح للتحميل البطيء ، منفصل عن defineAsyncComponent
. راجع توجيهات التحميل الخامل للمزيد من التفاصيل.
تحديث التحسينات
استقرار الخاصيات
في Vue ، يحدّث المكون الابن فقط عندما تتغير على الأقل واحدة من الخاصيات المستلمة. تأمل المثال التالي:
template
<ListItem
v-for="item in list"
:id="item.id"
:active-id="activeId" />
داخل المكون <ListItem>
، يستخدم خاصيتي id
و activeId
لتحديد ما إذا كان العنصر الحالي هو العنصر النشط أم لا. رغم أن هذا يشتغل كما هو مطلوب ، تكمن المشكلة في أنه كلما تغيرت activeId
، يجب تحديث كل <ListItem>
في القائمة!
بشكل مثالي، يجب فقط تحديث العناصر التي تغيّرت حالتها النشطة. يمكننا تحقيق ذلك عن طريق نقل حساب الحالة النشطة إلى المكون الأب، وجعل<ListItem>
يقبل مباشرةً خاصية active
بدلاً من ذلك:
template
<ListItem
v-for="item in list"
:id="item.id"
:active="item.id === activeId" />
الآن ، بالنسبة لمعظم المكونات ، ستظل الخاصية active
كما هي عند تغيير activeId
، لذلك لم تعد بحاجة إلى التحديث. بشكل عام ، الفكرة هي الحفاظ على استقرار الخاصيات التي تنتقل إلى المكونات الأبناء قدر الإمكان.
v-once
v-once
هي مُوجهة مدمجة تُستخدم لتصيير المحتوى الذي يعتمد على بيانات وقت التشغيل ولكنها لا تحتاج إلى التحديث مطلقًا. ستُتخطى الشجرة الفرعية sub-tree التي اُستخدم عليها لجميع التحديثات المستقبلية بشكل كامل. اطلع على مرجع واجهة برمجة التطبيق (API) للحصول على مزيد من التفاصيل
v-memo
v-memo
هي موجهة مدمجة يمكن استخدامها شرطيا لتخطي تحديث الأشجار الفرعية sub-trees الكبيرة أو قوائم v-for
. اطلع على مرجع واجهة برمجة التطبيق (API) للحصول على مزيد من التفاصيل.
تحسينات عامة
تؤثر النصائح التالية على كلٍ من تحميل الصفحة و تحديث الأداء.
جعل القوائم الكبيرة افتراضية
أحد أكثر مشاكل الأداء شيوعًا في جميع تطبيقات الواجهة الأمامية هو عرض قوائم كبيرة. بغض النظر عن مدى أداء إطار العمل ، فإن عرض قائمة بآلاف العناصر سيكون بطيئًا نظرًا للعدد الهائل من عُقد DOM التي يحتاج المتصفح للتعامل معها.
لكن، لا يتعين علينا بالضرورة عرض كل هذه العقد مقدمًا. في معظم الحالات ، يمكن لحجم شاشة المستخدم عرض مجموعة فرعية صغيرة فقط من قائمتنا الكبيرة. يمكننا تحسين الأداء بشكل كبير بجعل القوائم افتراضية ، وهي تقنية عرض العناصر الموجودة حاليًا أو القريبة من الإطار المعروض فقط في قائمة كبيرة.
إن إنجاز افتراضية القائمة ليس بالأمر السهل ، لحسن الحظ هناك مكتبات موجودة يمكنك استخدامها مباشرة:
تقليل التكلفة التفاعلية للهياكل الكبيرة غير القابلة للتغيير
نظام التفاعلية لـVue عميق بشكل افتراضي. على الرغم من أن هذا يجعل إدارة الحالة بديهية ، إلا أنه ينشئ مستوى معينًا من الحمل الزائد عندما يكون حجم البيانات كبيرًا ، لأن كل وصول إلى الخاصيات يقوم بتشغيل اعتراضات الوسيط التي تقوم بتتبع الإعتماديات. يصبح هذا ملحوظًا عادةً عند التعامل مع مصفوفات كبيرة من الكائنات المتداخلة بعمق ، حيث يحتاج التصيير الفردي إلى الوصول إلى أكثر من 100000 خاصية ، لذلك يجب أن يؤثر فقط على استخدام محدد للغاية.
توفر Vue طريقة للإفلات من التفاعلات العميقة باستخدام shallowRef()
و shallowReactive()
. تُنشئ الواجهات البرمجية السطحية حالة تفاعلية فقط على مستوى الجذر ، وتبقي جميع الكائنات المتداخلة كما هي. هذا يحافظ على الوصول السريع إلى الخاصيات المتداخلة ، مع التضحية بأنه يجب علينا الآن التعامل مع جميع الكائنات المتداخلة على أنها غير قابلة للتغيير ، ولا يمكن تشغيل التحديثات إلا عن طريق استبدال حالة الجذر:
js
const shallowArray = shallowRef([
/* قائمة كبيرة من الكائنات العميقة */
])
// هذا لن يؤدي إلى التحديثات...
shallowArray.value.push(newObject)
// هذا يؤدي:
shallowArray.value = [...shallowArray.value, newObject]
// هذا لن يؤدي إلى التحديثات...
shallowArray.value[0].foo = 1
// هذا يؤدي:
shallowArray.value = [
{
...shallowArray.value[0],
foo: 1
},
...shallowArray.value.slice(1)
]
تجنب التجريدات الغير ضرورية للمكون
في بعض الأحيان ، قد ننشئ مكونات عديمة التصيير أو مكونات ذات ترتيب أعلى (أي مكونات تُصيّر مكونات أخرى مع خاصيات إضافية) من أجل تجريد أفضل أو تنظيم للشيفرة. على الرغم من عدم وجود خطأ في هذا الأمر ، ضع في اعتبارك أن نسخ المكون أكثر تكلفة بكثير من عناصر الـDOM العادية ، وأن إنشاء الكثير منها باتباع أنماط التجريد سينجر عنه تكاليف في الأداء.
تجدر الملاحظة أن حذف عدد قليل فقط من النسخ لن يكون له تأثير ملحوظ ، لذلك لا تقلق إذا صُيِّر المكون عدة مرات فقط في التطبيق. أفضل سيناريو للنظر في هذا التحسين هو مرة أخرى في القوائم الكبيرة. تخيل قائمة تضم 100 عنصر حيث يحتوي كل مكون عنصر على العديد من المكونات الأبناء. قد تؤدي إزالة أحد تجريدات المكون غير الضرورية هنا إلى تقليل المئات من نسخ المكون.