تمرير البيانات بعمق باستخدام السياق
عادةً، ستقوم بتمرير المعلومات من المكون الأب إلى المكون الابن عبر الخصائص (props). لكن تمرير الخصائص يمكن أن يصبح مطولاً وغير مريح إذا اضطررت لتمريرها عبر العديد من المكونات الوسيطة، أو إذا احتاجت العديد من المكونات في تطبيقك إلى نفس المعلومات.السياقيتيح للمكون الأب جعل بعض المعلومات متاحة لأي مكون في الشجرة أسفله—بغض النظر عن العمق—دون تمريرها صراحةً عبر الخصائص.
سوف تتعلم
- ما هو "الحفر عبر الخصائص" (prop drilling)
- كيفية استبدال تمرير الخصائص المتكرر بالسياق
- حالات الاستخدام الشائعة للسياق
- البدائل الشائعة للسياق
المشكلة في تمرير الخصائص
تمرير الخصائصهو طريقة رائعة لتمرير البيانات بشكل صريح عبر شجرة واجهة المستخدم إلى المكونات التي تستخدمها.
لكن تمرير الخصائص يمكن أن يصبح مطولاً وغير مريح عندما تحتاج إلى تمرير خاصية ما بعمق عبر الشجرة، أو إذا احتاجت العديد من المكونات إلى نفس الخاصية. قد يكون السلف المشترك الأقرب بعيدًا جدًا عن المكونات التي تحتاج البيانات، ورفع الحالة لأعلىإلى ذلك المستوى يمكن أن يؤدي إلى موقف يسمى "الحفر عبر الخصائص".
رفع الحالة لأعلى


الحفر عبر الخصائص


ألن يكون رائعًا إذا كانت هناك طريقة "لنقل" البيانات إلى المكونات في الشجرة التي تحتاجها دون تمرير الخصائص؟ مع ميزة السياق في React، هناك طريقة!
السياق: بديل لتمرير الخصائص
يتيح السياق للمكون الأب توفير البيانات لكامل الشجرة أسفله. هناك العديد من استخدامات السياق. إليك مثال واحد. ضع في اعتبارك مكونHeadingهذا الذي يقبلlevelلحجمه:
لنفترض أنك تريد أن يكون للعناوين المتعددة داخل نفسSectionنفس الحجم دائمًا:
حاليًا، تمرر خاصيةlevelإلى كل<Heading>بشكل منفصل:
سيكون من الجيد إذا استطعت تمرير خاصيةlevelإلى مكون<Section>بدلاً من ذلك وإزالتها من مكون<Heading>. بهذه الطريقة يمكنك فرض أن جميع العناوين في نفس القسم لها نفس الحجم:
لكن كيف يمكن لمكون<Heading>معرفة مستوى أقرب مكون<Section>إليه؟هذا يتطلب طريقة ما لكي "يسأل" المكون الفرعي عن بيانات من مكان ما أعلى في الشجرة.
لا يمكنك فعل ذلك باستخدام الخصائص (props) وحدها. هنا يأتي دور السياق (context). ستقوم بذلك في ثلاث خطوات:
- أنشئسياقًا. (يمكنك تسميته
LevelContext، لأنه لمستوى العنوان.) - استخدمذلك السياق من المكون الذي يحتاج إلى البيانات. (سيستخدم
HeadingLevelContext.) - وفرذلك السياق من المكون الذي يحدد البيانات. (سيوفر
SectionLevelContext.)
يسمح السياق للمكون الأب — حتى لو كان بعيدًا! — بتوفير بعض البيانات لجميع الشجرة الموجودة بداخله.
استخدام السياق في المكونات الفرعية القريبة


استخدام السياق في المكونات الفرعية البعيدة


الخطوة 1: إنشاء السياق
أولاً، تحتاج إلى إنشاء السياق. ستحتاج إلىتصديره من ملفحتى تتمكن مكوناتك من استخدامه:
الوسيط الوحيد لـcreateContextهو القيمةالافتراضية. هنا، يشير1إلى مستوى العنوان الأكبر، لكن يمكنك تمرير أي نوع من القيم (حتى كائن). سترى أهمية القيمة الافتراضية في الخطوة التالية.
الخطوة 2: استخدام السياق
استورد خطافuseContextمن React والسياق الخاص بك:
حاليًا، يقرأ مكونHeading levelمن الخصائص:
بدلاً من ذلك، احذف خاصيةlevelواقرأ القيمة من السياق الذي استوردته للتو،LevelContext:
useContextهو خطاف. تمامًا مثلuseState و useReducer، يمكنك استدعاء الخطاف فقط مباشرة داخل مكون React (وليس داخل الحلقات أو الشروط).useContextيخبر React أن مكونHeadingيريد قراءةLevelContext.
الآن بما أن مكونHeadingليس لديه خاصيةlevel، فلست بحاجة إلى تمرير خاصية level إلىHeadingفي JSX الخاص بك هكذا بعد الآن:
قم بتحديث JSX بحيث تكونSectionهي التي تستقبله بدلاً من ذلك:
كتذكير، هذا هو الترميز الذي كنت تحاول جعله يعمل:
لاحظ أن هذا المثال لا يعمل بشكل كامل بعد! جميع العناوين لها نفس الحجم لأنعلى الرغم من أنكتستخدمالسياق، إلا أنك لمتقدمهبعد.React لا تعرف من أين تحصل عليه!
إذا لم تقدم السياق، ستستخدم React القيمة الافتراضية التي حددتها في الخطوة السابقة. في هذا المثال، حددت1كوسيطة لـcreateContext، لذا فإنuseContext(LevelContext)تُرجع1، مما يجعل جميع هذه العناوين<h1>. دعنا نحل هذه المشكلة عن طريق جعل كلSectionتقدم سياقها الخاص.
الخطوة 3: تقديم السياق
مكونSectionحاليًا يقوم بعرض العناصر الفرعية الخاصة به:
قم بتغليفها بمزود سياقلتوفيرLevelContextلها:
هذا يخبر React: "إذا طلب أي مكون داخل هذا<Section> LevelContext، فامنحهم هذاlevel." سيستخدم المكون قيمة أقرب<LevelContext>في شجرة واجهة المستخدم فوقه.
إنها نفس نتيجة الكود الأصلي، لكنك لم تكن بحاجة إلى تمرير خاصيةlevelإلى كل مكونHeading! بدلاً من ذلك، "يستنتج" مستوى العنوان الخاص به عن طريق سؤال أقربSectionأعلاه:
- تمرر خاصية
levelإلى المكون<Section>. Sectionيلف العناصر الفرعية الخاصة به داخل<LevelContext value={level}>.Headingيطلب أقرب قيمة لـLevelContextالموجودة أعلاه باستخدامuseContext(LevelContext).
استخدام وتوفير السياق من نفس المكون
حاليًا، لا يزال عليك تحديدlevelلكل قسم يدويًا:
نظرًا لأن السياق يتيح لك قراءة المعلومات من مكون أعلاه، يمكن لكل مكونSectionقراءةlevelمن المكونSectionالموجود أعلاه، وتمريرlevel + 1إلى الأسفل تلقائيًا. إليك كيف يمكنك القيام بذلك:
مع هذا التغيير، لم تعد بحاجة إلى تمرير خاصيةlevel أيضًاإلى<Section>أو إلى<Heading>:
الآن كلا المكونينHeading و SectionيقرآنLevelContextلمعرفة مدى "عمق" وجودهما. ويقوم المكونSectionبتغليف عناصره الفرعية داخلLevelContextلتحديد أن أي شيء بداخله يكون في مستوى "أعمق".
ملاحظة
يستخدم هذا المثال مستويات العناوين لأنها تُظهر بصريًا كيف يمكن للمكونات المتداخلة تجاوز السياق. لكن السياق مفيد أيضًا للعديد من حالات الاستخدام الأخرى. يمكنك تمرير أي معلومات يحتاجها الشجرة الفرعية بأكملها: سمة اللون الحالية، المستخدم المسجل حاليًا، وهكذا.
السياق يمر عبر المكونات الوسيطة
يمكنك إدراج أي عدد تريده من المكونات بين المكون الذي يوفر السياق والمكون الذي يستخدمه. وهذا يشمل كلاً من المكونات المدمجة مثل<div>والمكونات التي قد تبنيها بنفسك.
في هذا المثال، يتم عرض نفس مكونPost(بحدود متقطعة) على مستويين تداخل مختلفين. لاحظ أن<Heading>الموجود بداخله يحصل على مستواه تلقائيًا من أقرب مكون<Section>:
لم تقم بأي شيء خاص لجعل هذا يعمل. تحددSectionالسياق للشجرة الموجودة بداخلها، لذا يمكنك إدراج<Heading>في أي مكان، وسيكون له الحجم الصحيح. جربه في الحقل الرملي أعلاه!
يسمح لك السياق بكتابة مكونات "تتكيف مع محيطها" وتعرض نفسها بشكل مختلف اعتمادًا علىأين(أو بعبارة أخرى،في أي سياق) يتم عرضها.
قد تذكرك طريقة عمل السياق بـوراثة خصائص CSS.في CSS، يمكنك تحديدcolor: blue لـ <div>، وأي عقدة DOM بداخلها، بغض النظر عن العمق، سترث هذا اللون ما لم تقم عقدة DOM أخرى في المنتصف بتجاوزه باستخدامcolor: green. وبالمثل، في React، فإن الطريقة الوحيدة لتجاوز بعض السياق القادم من الأعلى هي لف المكونات الفرعية داخل موفر سياق بقيمة مختلفة.
في CSS، الخصائص المختلفة مثلcolor و background-colorلا تتجاوز بعضها البعض. يمكنك تعيين<div>لـcolorإلى أحمر دون التأثير علىbackground-color. وبالمثل،سياقات React المختلفة لا تتجاوز بعضها البعض.كل سياق تقوم بإنشائه باستخدامcreateContext()منفصل تمامًا عن السياقات الأخرى، ويربط المكونات التي تستخدم وتوفرذلك السياق المحدد.يمكن لمكون واحد استخدام أو توفير العديد من السياقات المختلفة دون مشكلة.
قبل استخدام السياق
السياق مغري جدًا للاستخدام! ومع ذلك، هذا يعني أيضًا أنه من السهل جدًا الإفراط في استخدامه.مجرد أنك تحتاج إلى تمرير بعض الخصائص عبر عدة مستويات لا يعني أنه يجب عليك وضع تلك المعلومات في السياق.
فيما يلي بعض البدائل التي يجب مراعاتها قبل استخدام السياق:
- ابدأ بـتمرير الخصائص.إذا لم تكن مكوناتك تافهة، فليس من غير المعتاد تمرير عشرات الخصائص عبر عشرات المكونات. قد يبدو الأمر شاقًا، لكنه يجعل من الواضح جدًا أي المكونات تستخدم أي بيانات! سيكون الشخص الذي يحافظ على الكود الخاص بك سعيدًا لأنك جعلت تدفق البيانات صريحًا باستخدام الخصائص.
- استخرج المكونات ومرر JSX كمكونات فرعيةإليها.إذا قمت بتمرير بعض البيانات عبر العديد من طبقات المكونات الوسيطة التي لا تستخدم تلك البيانات (وتقوم فقط بتمريرها إلى الأسفل)، فهذا يعني غالبًا أنك نسيت استخراج بعض المكونات على طول الطريق. على سبيل المثال، ربما تمرر خصائص بيانات مثل
postsإلى مكونات مرئية لا تستخدمها مباشرة، مثل<Layout posts={posts} />. بدلاً من ذلك، اجعلLayoutيأخذchildrenكخاصية، وقم بعرض<Layout><Posts posts={posts} /></Layout>. هذا يقلل عدد الطبقات بين المكون الذي يحدد البيانات والمكون الذي يحتاجها.
إذا لم تنجح أي من هاتين الطريقتين بشكل جيد بالنسبة لك، ففكر في استخدام السياق.
حالات استخدام السياق
- التنسيق:إذا كان تطبيقك يسمح للمستخدم بتغيير مظهره (مثل الوضع الداكن)، يمكنك وضع موفر سياق في أعلى تطبيقك، واستخدام ذلك السياق في المكونات التي تحتاج إلى ضبط مظهرها المرئي.
- الحساب الحالي:قد تحتاج العديد من المكونات إلى معرفة المستخدم المسجل دخوله حاليًا. وضعه في سياق يجعل قراءته في أي مكان في الشجرة أمرًا مريحًا. تسمح بعض التطبيقات أيضًا بتشغيل حسابات متعددة في نفس الوقت (مثل ترك تعليق كمستخدم مختلف). في تلك الحالات، يمكن أن يكون من المناسب تغليف جزء من واجهة المستخدم داخل موفر متداخل بقيمة حساب حالي مختلفة.
- التوجيه:تستخدم معظم حلول التوجيه السياق داخليًا للاحتفاظ بالمسار الحالي. هذه هي الطريقة التي "تعرف" بها كل رابط ما إذا كانت نشطة أم لا. إذا كنت تبني جهاز التوجيه الخاص بك، فقد ترغب في فعل ذلك أيضًا.
- إدارة الحالة:مع نمو تطبيقك، قد ينتهي بك الأمر بالحصول على الكثير من الحالة بالقرب من أعلى تطبيقك. قد ترغب العديد من المكونات البعيدة أدناه في تغييرها. من الشائعاستخدام مخفض مع السياقلإدارة الحالة المعقدة وتمريرها إلى المكونات البعيدة دون متاعب كثيرة.
السياق لا يقتصر على القيم الثابتة. إذا قمت بتمرير قيمة مختلفة في عملية التصيير التالية، فإن React ستقوم بتحديث جميع المكونات التي تقرأه أدناه! لهذا السبب غالبًا ما يستخدم السياق بالاشتراك مع الحالة.
بشكل عام، إذا كانت بعض المعلومات مطلوبة من قبل مكونات بعيدة في أجزاء مختلفة من الشجرة، فهذا مؤشر جيد على أن السياق سيساعدك.
ملخص
- يسمح السياق للمكون بتوفير بعض المعلومات لجميع الشجرة الموجودة أسفله.
- لتمرير السياق:
- قم بإنشائه وتصديره باستخدام
export const MyContext = createContext(defaultValue). - مرره إلى خطاف
useContext(MyContext)لقراءته في أي مكون فرعي، بغض النظر عن العمق. - غلف العناصر الفرعية داخل
<MyContext value={...}>لتوفيره من أحد الوالدين.
- قم بإنشائه وتصديره باستخدام
- يمر السياق عبر أي مكونات في المنتصف.
- يسمح لك السياق بكتابة مكونات "تتكيف مع محيطها".
- قبل استخدام السياق، حاول تمرير الخصائص أو تمرير JSX كـ
children.
جرب بعض التحديات
Challenge 1 of 1:استبدال تمرير الخصائص (Prop Drilling) بالسياق (Context) #
في هذا المثال، يؤدي تبديل خانة الاختيار إلى تغيير خاصية imageSize التي يتم تمريرها إلى كل مكون <PlaceImage>. يتم الاحتفاظ بحالة خانة الاختيار في مكون المستوى الأعلى App، لكن كل مكون <PlaceImage> يحتاج إلى أن يكون على علم بها.
حاليًا، يمرر مكون App خاصية imageSize إلى مكون List، والذي يمررها إلى كل مكون Place، والذي يمررها إلى مكون PlaceImage. قم بإزالة خاصية imageSize، وبدلاً من ذلك قم بتمريرها من مكون App مباشرة إلى مكون PlaceImage.
يمكنك تعريف السياق في ملف Context.js.
