Kodokon kodokon.com

Context: المشاركة دون prop drilling

انقل البيانات دون prop drilling باستخدام context، مع إبقاء عمليات إعادة تصييره وحدوده تحت السيطرة.

8 دقيقة · 3 أسئلة

افتح هذا الدرس في Kodokon

يشير prop drilling إلى تلك الـ props التي تمرّ عبر ثلاثة أو أربعة مكوّنات وسيطة دون أن تُستخدم فيها، فقط لتصل إلى ورقة في الشجرة. كل طبقة تعبرها تصبح مرتبطة ببيانات لا تعنيها. يحلّ context مشكلة النقل هذه: createContext يُنشئ القناة، ومزوّد ينشر قيمة، و useContext يقرؤها من أي سليل. احتفظ بالفارق الدقيق في ذهنك: context ينقل الحالة، ولا يديرها - القيمة لا تزال تعيش في مكوّن، هنا المزوّد.

JSX
import { createContext, useMemo, useState } from "react";

const ThemeContext = createContext(null);

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("dark");
  const value = useMemo(
    () => ({ theme, setTheme }),
    [theme]
  );

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}
القيمة المنشورة مُذاكَرة (memoized) لإبقاء مرجعها مستقرًا.

عندما تتغير قيمة المزوّد، يُعاد تصيير كل المستهلكين، حتى المتداخلين بعمق - و React.memo لا يحميهم، لأنه يقارن الـ props وليس الـ context. يزيد الفخّ الكلاسيكي الأمور سوءًا: كتابة value={{ theme, setTheme }} تُنشئ كائنًا جديدًا في كل تصيير للمزوّد، ومن ثم مرجعًا جديدًا، ومن ثم إعادات تصيير متسلسلة حتى عندما لم يتحرك theme. لهذا وُجد useMemo أعلاه. أكمل الإعداد بخطّاف وصول يفشل بصوت عالٍ خارج المزوّد.

JSX
import { useContext } from "react";

function useTheme() {
  const context = useContext(ThemeContext);
  if (context === null) {
    throw new Error(
      "useTheme must be inside <ThemeProvider>"
    );
  }
  return context;
}
خطأ صريح أفضل من null صامت.

قبل إنشاء context، استنفد خيار التركيب (composition) أولاً. إذا كانت طبقة وسيطة لا تفعل شيئًا سوى ترحيل prop، فاعكس البنية: ابنِ العنصر في أعلى الشجرة، حيث تعيش البيانات، ومرّره إلى الأسفل عبر children أو prop مخصص. تصبح الطبقة الوسيطة عامة ولا تمرّ البيانات بعد ذلك عبر أي مكوّن لا يستخدمها.

JSX
function App({ currentUser }) {
  return (
    <Layout
      header={<TopBar user={currentUser} />}
      content={<Dashboard />}
    />
  );
}
Layout لا يعرف شيئًا عن currentUser: بل يُدرِج عناصر جاهزة.

اختبار المعرفة

تأكّد من أنك تذكّرت النقاط الأساسية في هذا الدرس.

  1. مستهلك context مُغلَّف بـ React.memo وتتغير قيمة الـ context. ماذا يحدث؟
    • لا يُعاد تصييره: React.memo يمنع كل التحديثات
    • يُعاد تصييره: React.memo يقارن الـ props، لا قيم الـ context
    • يُعاد تصييره فقط إذا تغيرت props الخاصة به أيضًا
  2. لماذا نُذاكِر (memoize) الكائن المُمرَّر إلى value في مزوّد؟
    • لتسريع إنشاء الكائن في كل تصيير
    • لأن المزوّد يتطلب قيمة غير قابلة للتغيير
    • كي لا يتغير المرجع إلا عندما تتغير البيانات، تفاديًا لإعادات تصيير غير ضرورية للمستهلكين
  3. ما القيد الرئيسي لـ context بالنسبة للحالة التي تُحدَّث بتواتر عالٍ جدًا؟
    • لا مُنتقِيات: كل تحديث يعيد تصيير جميع المستهلكين، حتى الذين يستخدمون جزءًا من القيمة فقط
    • الـ context غير متزامن ويؤخّر التحديثات
    • لا يمكن للـ context تخزين سوى القيم الأولية