إصلاح INP: كيف نضبط التفاعل حتى الرسم التالي في React
جدول المحتويات
عن الكاتب

Ahmed Mahmoud
كاتب ومطوّر
مهندس برمجيات شغوف بتطوير الويب وتصميم تجربة المستخدم.
مؤسّس ديفيا · eng-ahmed.com ↗أبرز النقاط
مقياس INP (التفاعل حتى الرسم التالي) يقيس زمن الاستجابة من تفاعل المستخدم حتى الإطار التالي الذي يرسمه المتصفح؛ 200 مللي ثانية أو أقل جيد، وأكثر من 500 مللي ثانية ضعيف، وكلاهما عند المئين الخامس والسبعين.
حلّ INP محل مقياس First Input Delay (FID) ضمن Core Web Vitals في مارس 2024 — إذ كان FID يقيس تأخير الإدخال للتفاعل الأول فقط، بينما يقيس INP الاستجابة الكاملة لأغلب التفاعلات طوال عمر الصفحة.
ينقسم كل تفاعل إلى ثلاث مراحل: تأخير الإدخال، ووقت المعالجة، وتأخير العرض. في ديفيا نُحسّن المرحلة المهيمنة فعليًا بدلًا من ضبط الثلاث معًا دون تمييز.
أكثر إصلاحين فاعلية في React هما تغليف تحديثات الحالة غير العاجلة داخل startTransition، وتقسيم المهام الطويلة عبر واجهة المتصفح scheduler.yield().
INP مقياس ميداني. نقيسه بمكتبة web-vitals أو عبر PerformanceObserver وليس عبر Lighthouse، لأن أداة المختبر لا تستطيع محاكاة تفاعلات المستخدمين الحقيقية.
ما هو INP وكيف يختلف عن FID؟
INP هو مقياس Core Web Vitals الذي يبلّغ عن أطول زمن استجابة لتفاعل يمر به المستخدم على الصفحة، من لحظة النقر أو اللمس أو الضغط على مفتاح حتى الإطار التالي الذي يرسمه المتصفح. رقّت Google مقياس INP من تجريبي إلى مقياس أساسي في مارس 2024 وأحالت FID للتقاعد.
الفرق مهم. كان FID يقيس تأخير الإدخال للتفاعل الأول فقط ويتجاهل كل ما يحدث بعد بدء معالج الحدث. أما INP فيقيس التفاعل كاملًا — تأخير الإدخال زائد عمل المعالج زائد العرض — ويبلّغ عن الأسوأ خلال الجلسة. قد تسجّل الصفحة FID مثاليًا وتظل تبدو معطلة، لأن FID لم يرَ إعادة الرسم البطيئة التي أطلقها النقر.
ما مراحل التفاعل الثلاث؟
ينقسم كل قياس لـ INP إلى ثلاث مراحل، ومعرفة المرحلة الطويلة هي جوهر المسألة. تأخير الإدخال هو الزمن من فعل المستخدم حتى بدء معالج الحدث، ويُقضى غالبًا في انتظار المسار الرئيسي حتى ينهي عملًا آخر.
وقت المعالجة هو المدة التي تستغرقها معالجات الأحداث في التنفيذ، بما في ذلك تحديثات حالة React وأي عمل متزامن تُطلقه. تأخير العرض هو الزمن من نهاية المعالجة حتى يرسم المتصفح الإطار التالي، وتهيمن عليه عمليات التخطيط والتنسيق والرسم.
تُظهر لوحة الأداء في أدوات مطوري Chrome هذه المراحل الثلاث عند تسجيل تفاعل. قاعدتنا أن نُحسّن المرحلة الطويلة فعليًا: في لوحة تحكم أحد العملاء كانت المشكلة الحقيقية تأخير إدخال من سكربت تحليلات غير مرتبط، لا وقت المعالجة الذي اشتبهنا به أولًا.
كيف نُصلح تأخير الإدخال؟
تأخير الإدخال يكون غالبًا بسبب انشغال المسار الرئيسي بمهمة طويلة لحظة نقر المستخدم. المهمة الطويلة هي أي كتلة JavaScript تشغل المسار الرئيسي لأكثر من 50 مللي ثانية. الحل هو تقسيم المهام الطويلة إلى أجزاء أصغر وإعادة التحكم للمتصفح بينها كي يُعالَج النقر المُعلّق.
واجهة المتصفح الحديثة لذلك هي scheduler.yield()، التي تُعيد وعدًا يستأنف بعد أن يعالج المتصفح العمل الأعلى أولوية مثل الإدخال. حين لا تتوفر، يكون انتظار setTimeout بصفر هو البديل القديم، وإن استأنف بأولوية أدنى. أكبر مكسب لنا في أحد المشاريع كان نقل تهيئة تحليلات خارجية بعيدًا عن التحميل الأولي عبر requestIdleCallback كي تتوقف عن حجب أوائل النقرات.
كيف نُصلح وقت المعالجة؟
وقت المعالجة هو حيث تعمل React، والأداة هي إخبار React بأي التحديثات عاجل. في React 18 و19، يُعلّم startTransition تحديث الحالة على أنه غير عاجل كي تتمكن React من مقاطعته للحفاظ على استجابة التفاعل. النمط المعتاد هو ضبط قيمة الإدخال بشكل عاجل مع كل ضغطة مفتاح، مع تغليف تحديث القائمة المُفلترة المكلف داخل startTransition ليُعاد رسمه بأولوية أدنى.
الخطاف useDeferredValue يفعل الأمر نفسه لقيمة تستقبلها بدل أن تضبطها. لا يجعل أيٌّ منهما الفلترة أسرع — بل يجعلها قابلة للمقاطعة، وهو بالضبط ما يقيسه INP. أما الحسابات الثقيلة فعلًا فننقلها خارج المسار الرئيسي كليًا إلى Web Worker.
كيف نُصلح تأخير العرض؟
تأخير العرض هو عمل الرسم لدى المتصفح بعد انتهاء المعالج، والسبب المعتاد هو مطالبته بتخطيط ورسم قدر من DOM أكبر مما يراه المستخدم. خاصية CSS المسماة content-visibility بقيمة auto تُخبر المتصفح بتخطي رسم العناصر خارج الشاشة حتى تقترب من نافذة العرض، واقترانها بـ contain-intrinsic-size يحجز ارتفاعًا كي يبقى شريط التمرير ثابتًا.
السبب الشائع الآخر هو ارتباك التخطيط: قراءة خاصية تخطيط مثل offsetHeight ثم كتابة نمط في الحلقة نفسها يفرض إعادة تدفق متزامنة. اجمع كل القراءات ثم كل الكتابات. وللقوائم الطويلة جدًا، الافتراضية — رسم الصفوف المرئية فقط — تتفوق على أي حيلة CSS.
المراحل الثلاث في لمحة
تأخير الإدخال يمتد من الفعل حتى بدء المعالج؛ سببه الشائع مهمة طويلة تحجب المسار الرئيسي، وأول إصلاح لنا هو scheduler.yield() مع تأجيل السكربتات الخارجية.
وقت المعالجة هو تنفيذ المعالج؛ سببه الشائع إعادة رسم متزامنة مكلفة، وأول إصلاح لنا هو startTransition أو useDeferredValue أو Web Worker. تأخير العرض يمتد من نهاية المعالج حتى الرسم التالي؛ سببه الشائع DOM كبير أو ارتباك تخطيط، وأول إصلاح لنا هو content-visibility والافتراضية وتجميع القراءات والكتابات.
كيف نقيس INP ميدانيًا؟
INP مقياس ميداني، لذا الرقم المهم يأتي من مستخدمين حقيقيين لا من Lighthouse. لا يستطيع Lighthouse النقر على الأزرار، فقياسه المخبري لـ INP تقدير اصطناعي. نلتقط الرقم الحقيقي عبر دالة onINP في مكتبة web-vitals، ونرسل كل قياس إلى التحليلات باستخدام navigator.sendBeacon.
نسخة الإسناد في web-vitals تبلّغ عن العنصر والمرحلة اللذين سبّبا أسوأ تفاعل، فنُصلح الشيء الصحيح. ثم تؤكد بيانات Chrome الميدانية في تقرير CrUX وفي PageSpeed Insights ما إذا كان التغيير قد حرّك درجة المئين الخامس والسبعين التي يختبرها المستخدمون فعلًا.
الأسئلة الشائعة
س: ما درجة INP الجيدة؟
ج: 200 مللي ثانية أو أقل عند المئين الخامس والسبعين جيدة، ومن 200 إلى 500 مللي ثانية تحتاج تحسينًا، وأكثر من 500 مللي ثانية ضعيفة.
س: هل INP من Core Web Vitals؟
ج: نعم. أصبح INP أحد مقاييس Core Web Vitals الثلاثة في مارس 2024، ليحل محل First Input Delay.
س: هل يجعل startTransition الشيفرة أسرع؟
ج: لا. إنه يُعلّم تحديث الحالة على أنه غير عاجل كي تتمكن React من مقاطعته؛ يستغرق العمل الوقت نفسه لكنه يتوقف عن حجب التفاعل.
س: هل يمكننا قياس INP في Lighthouse؟
ج: ليس بموثوقية. يعمل Lighthouse في مختبر دون تفاعلات حقيقية؛ استخدم مكتبة web-vitals أو بيانات CrUX الميدانية من Chrome لقياس INP حقيقي.
س: ما هو scheduler.yield()؟
ج: واجهة متصفح توقف مهمة طويلة وتستأنفها بعد أن يعالج المتصفح العمل الأعلى أولوية مثل إدخال المستخدم، مبقيةً تأخير الإدخال منخفضًا.
قراءات إضافية
هندسة الواجهات
الوضع الداكن دون وميض في Next.js 16: كيف نستخدم سكربت سمة حاجبًا وcolor-scheme وزر تبديل آمنًا مع الـ Hydration
وميض الوضع الداكن عند إعادة تحميل الصفحة مشكلة توقيت: الخادم لا يستطيع قراءة localStorage. نشرح سكربت السمة الحاجب الذي نستخدمه في Next.js 16، ولماذا لا نضع السمة في cookie حفاظًا على الرسم الثابت، وكيف نبني زر تبديل يمر بالـ hydration دون أي عدم تطابق.
هندسة الواجهات
صور Open Graph الديناميكية في Next.js 16: كيف نبني opengraph-image.tsx ونتحقق من إصدار Satori الذي تضمّنه next/og ونحافظ على صحة معاينات الروابط
واجهت بطاقات المشاركة المولّدة لدينا ثلاث مفاجآت: خط WOFF2 لم يستطع المحرّك قراءته، وملف README لـ Satori يصف محرّكًا أحدث من المضمّن في Next.js، وصفحة ظلّت تعرض صورة الـ layout. هذه ملاحظاتنا الميدانية حول opengraph-image.tsx في Next.js 16 وأولوية البيانات الوصفية وفحص og:image قبل مشاركة أي رابط.