دورة TanStack Router الكاملة › دورة TanStack Router الكاملة 1 / 1 السابق التالي

تمرير الحالة في السياق (Passing State in Context)

الدرس 19 من 34 · دورة TanStack Router الكاملة · 10 دقيقة و5 ثانية

الفيديو الأصلي باللغة الإنجليزية. إذا تعذّر تشغيله هنا، شاهده على موقع المؤلف. التفريغ النصي العربي الكامل أدناه.

مستودع الكود لهذا الدرس

الفرع: context-2

[00:02] إلى سياق TanStack Router لدينا وكي نتمكّن من التلاعب به في تطبيقنا، نحتاج إلى استخدام خطّاف useState. وكذلك، بالإضافة إلى الحالة (state)، ربّما تستخدم خطّافًا ما، خطّافًا آخر — ربّما موفّر مصادقة (authentication provider)، مثل useAuth0 مثلاً — وتحتاج إلى تمرير بيانات من useAuth0

[00:17] إلى سياقك. في هذه الحالة سَتَحتاج أيضًا إلى استخدام خطّاف (hook).

[00:23] وكما قد تعلم، لا يمكننا استخدام الخطّافات (hooks) إلّا داخل مكوّنات (components) React.

[00:27] هنا، عندما نُهيّئ هذا الموجّه (router) ونمرّر سياقنا، لسنا داخل مكوّن React. إذًا ما نحتاج إلى فعله — فلنُرجِع هذا إلى null — وما يمكننا فعله هو تهيئة هذا السياق بطريقة مختلفة. إذًا في

[00:42] مكوّن (component) موفّر الموجّه (RouterProvider) يمكننا تمرير السياق كذلك. إذًا ما سنفعله هو أنّنا سَنَنشئ مكوّنًا، فلنسمّه ربّما App أو ما شابه. إذًا فلنُنشئ دالّة (function) اسمها App.

[00:55] وهنا سَنقوم بالأشياء التي علينا القيام بها من أجل تهيئة سياقنا أو الحصول على سياقنا، ثم تمريره إلى RouterProvider. إذًا سأنسخ هذا — سأقصّه من هنا — وأُرجِعه هنا

[01:10] وأستخدم App هنا بدلًا من ذلك، صحيح؟ إذًا الآن أصبحت لَدَينا الفرصة لاستخدام الخطّافات (hooks) في مكوّن App هذا — استخدام useContext، واستخدام أيّ خطّاف من موفّر (provider) — وما إلى ذلك، وتمرير بيانات من هذا الموفّر أو من هذا الخطّاف إلى سياقنا بتمرير خاصيّة (prop) السياق (context prop)

[01:28] هنا، صحيح؟ فلنفعل ذلك. لكن أولًا، تذكّر أنّه كان لَدَينا هنا — في الحقيقة ليس هنا، بل في مكوّن الجذر (root component) هذا — هذا النوع (type) الخاصّ بمستخدمنا. وسنحتاج في الواقع إلى استخدامه في مواضع متعدّدة.

[01:41] مواضع. لذا سأقصّه من هنا وأضعه في مكان مشترك. فلنقل ربّما في مجلّد source لدينا — مجلّد src. فلنُنشئ مجلّدًا اسمه types ثم index.ts أو ما شابه ذلك، ثم فلنضع هذا هنا ونُصدِّره (export)

[01:57] كي نتمكّن من استخدامه هنا. فلنستورده (import) هنا.

[02:01] وسنحتاج إلى استخدامه هنا كذلك. إذًا فلنقل أنّ لَدَينا بعض الحالة (state). سأُنشئ حالة user و setUser وسنستخدم خطّاف useState، وستكون هذه الحالة من نوع

[02:20] user الذي يمكننا استيراده أو null، صحيح؟ فلنقل أنّنا سنُهيّئه إلى null.

[02:28] إذًا في البداية لسنا في وضع تسجيل الدخول (logged in)، والآن يمكننا ببساطة أن نمرّر هذه الحالة كسياقٍ لنا. إذًا هنا يمكننا تمرير user لدينا — عذرًا — داخل كائن (object) يمكننا تمرير user لدينا هكذا.

[02:42] من خلال القيام بذلك لَدَينا دالّة (function) setUser هذه التي يمكن أيضًا أن نمرّرها في سياقنا، ويمكن أن تكون متوفّرة في كلّ مكان كي نتمكّن من ضبط المستخدم إلى شيء آخر، ربّما عندما نسجّل الدخول أو الخروج وما إلى ذلك. ونحن

[02:56] بالطبع هذا مثال بسيط؛ في الواقع، ربّما تستخدم مكتبة مصادقة (authentication) ما، وسترى ربّما بعض المعلومات من خطّاف (hook) useAuth0، ربّما معلومات المستخدم وما شابه ذلك، ثم يمكنك تمريرها

[03:10] إلى سياقك أيضًا، لكنّنا هنا نُجري مجرّد مثال بسيط. فالآن بما أنّ لَدَينا null هنا سَنحصل على «مرحبًا أيّها الزائر»، لكن بالطبع يمكننا تغيير هذا إلى شيء ما، وأسف، فلنُعطِ اسمًا، وسَنحصل

[03:26] على هذا هنا، صحيح؟ لكن فلنتركه في الحقيقة null، والآن فلنمتلك نوعًا من الطرق كي نسجّل الدخول أو الخروج وما إلى ذلك ونضبط هذا المستخدم. إذًا ما سأفعله هو — تذكّر حين عرّفنا نوع سياقنا الذي

[03:43] هو هنا — فلنقل أنّه بالإضافة إلى المستخدم سَيَكون لَدَينا دالّة login تستقبل منّا كائن (object) من نوع user لتسجيل دخوله، وهي ستكون دالّة من نوع void.

[03:55] وسنمرّر هذه الدالّة في سياقنا هنا في الجذر (root) كي تكون متوفّرة في كلّ مكان، صحيح؟ وفلنمتلك أيضًا دالّة logout ستكون مجرّد دالّة من نوع void.

[04:07] وفي هذه الدالّة سَنضبط مستخدمنا إلى null.

[04:12] إذًا الآن بعد أن أصبح لَدَينا هذا في سياقنا نحتاج إلى تحديده هنا أيضًا، صحيح؟ إذًا ما سأفعله هو أنّني سأُعرِّف هذه الدوالّ (functions) هنا. فلنُنشئ دالّة نسمّيها login وسنمرّر إليها مستخدمًا

[04:26] نحتاج إلى تسجيل دخوله من نوع user، ثم هنا سَنستدعي ببساطة setUser ونضبطه إلى ما نمرّره إلى دالّتنا، صحيح؟ وبنفس الطريقة، فلنكرّر هذا فعليًا. نُنشئ logout.

[04:43] نضبط المستخدم إلى null. ولا نحتاج إلى هذا.

[04:47] والآن يمكننا ببساطة أن نمرّر هذه الدوالّ هنا. إذًا login و log out.

[04:52] أوه، عذرًا. log out.

[04:55] وما زلنا نحصل على خطأ. نعم، نحتاج أيضًا إلى تمرير هذه الدوالّ هنا، لأنّها جزء من سياقنا. إذًا هنا يمكننا تمرير دالّة فارغة (empty function) لـ login و logout، لأنّنا في نهاية المطاف سَنملؤها

[05:11] هذه الدوالّ في مكوّن (component) App هذا.

[05:15] إذًا الآن سَتَكون هذه الدوالّ متوفّرة في كلّ مكان في تطبيقنا. فلنحاول استخدامها، ربّما أيضًا في مكوّن (component) الجذر (root) لدينا. إذًا هنا، إذا كنّا في وضع تسجيل الدخول فلنضيف زرّ (button) تسجيل الخروج، وإذا كنّا في وضع

[05:29] عدم تسجيل الدخول فلنضيف زرّ تسجيل الدخول. فإذا كنّا في وضع تسجيل الدخول سأضيف زرًّا هنا، فلنقل log out، وعند النقر عليه نحتاج إلى استدعاء دالّة logout التي مرّرناها إلى سياقنا. فينبغي أن تكون متوفّرة هنا.

[05:46] فلنحصل أيضًا على login ولنحصل على logout.

[05:49] وهنا فلنستدعي log out. وفلننسخ هذا أيضًا. وإذا كنّا في وضع تسجيل الدخول، أه عذرًا، إذا كنّا في وضع عدم تسجيل الدخول فلنقل login، فلنستدعي login ونمرّر له مستخدمًا وهميًا (dummy user). إذًا سأستدعي

[06:06] login، وهنا فلنضيف ربّما مُعرِّفًا (ID) وبعض الاسم مثل test.

[06:14] إذًا الآن لَدَينا زرّ (button) login هذا. لكن إذا نقرنا عليه، كما ترى، لن يحدث أيّ شيء. وإذا حاولنا الانتقال إلى مسار (route) مختلف، كما ترى الآن أصبح محدَّثًا. فسنحصل على log out لدينا وسَنحصل على test هنا، لكن اضطُررنا

[06:31] إلى الانتقال إلى مسار مختلف كي يُحدَّث هذا. إذًا ما نحتاج إلى فعله — فلنُحدِّث الصفحة ربّما كي نكون خارج وضع تسجيل الدخول في البداية — ما نحتاج إلى فعله هو أنّه عندما نُحدِّث سياقنا عن طريق تحديث حالتنا التي نمرّرها إلى سياقنا، ينبغي

[06:45] تمريرها إلى سياقنا، نحتاج إلى استدعاء دالّة (function) تُسمّى router.invalidate كي يُعاد حساب سياقنا وتُحدَّث قيمته في واجهتنا (UI). إذًا هنا في main.tsx لَدَينا مسبقًا الوصول إلى الموجّه (router) الذي عرّفناه هنا. وإن لم

[07:01] نكن هنا يمكننا الوصول إلى هذا الموجّه باستخدام خطّاف (hook) useRouter. لكن لا داعي لذلك، لأنّنا أصبح لَدَينا مسبقًا الوصول إلى الموجّه (router). إذًا يمكننا ببساطة استدعاء router.invalidate هنا، ويمكننا فعل

[07:15] ذلك هنا أيضًا عندما نسجّل الخروج. غير أنّه إذا حفظنا هذا، ولنُحدِّث الصفحة أيضًا ربّما، فسَيَظلّ لا يعمل. وهذه في الحقيقة مُشكلة (issue) يمكننا إلقاء نظرة عليها هنا — يمكننا النظر إليها

[07:31] هنا. وهي لا تزال مفتوحة (opened) حتى الآن.

[07:34] لكن لَدَينا بضعة حلول بديلة (workarounds) لإصلاح هذه المشكلة. فما يحدث هو أنّنا نضبط المستخدم هنا. لكن، كما تعلم، عندما نضبط حالة (state) في React فإنّها لا تُضبط فورًا. إذًا عندما نُشغّل بعض الشيفرة

[07:48] بعد ضبط حالةٍ ما فلن يكون مضمونًا أنّ حالتنا قَدْ تحدّثت. وهذه على الأرجحَ ما يحدث: عندما نضبط هذا المستخدم فإنّ حالتنا لم تُضبط بعد، وبالتالي لن يفعل router.invalidate

[08:01] شيئًا. إذًا إحدى طرق إصلاح هذه المشكلة هي الانتظار ربّما لثانية. فلنجعل هذه الدالّة async وننتظر ثانية هنا قبل استدعاء router.invalidate، وفي هذه الحالة ينبغي أن يكون المستخدم قد ضُبط عند هذه النقطة.

[08:17] وهذا بالطبع ليس طريقة رائعة لإصلاح المشكلة. لكن إذا جرّبه، انقر تسجيل الدخول، انتظر ثانية. كما تلاحظ، الآن حصلنا على الواجهة (UI) المحدَّثة لدينا. لكن هذا ربّما طريقة hacky جدًا لفعلها. إحدى الطرق الأخرى هي استدعاء دالّة (function) تُسمّى

[08:33] flushSync. عذرًا، flushSync التي يمكننا استيرادها من ReactDOM. في هذه الدالّة، هذه الدالّة يمكن أن نمرّر إليها دالّة أخرى، وإذا ضبطنا أيّ حالة (state) داخل هذه الدالّة فسَتُضبط فورًا. فإذا فعلنا أيّ شيء آخر

[08:47] بعد استدعاء flushSync فسَيَكون لدينا ضمان بأنّ الحالة قَدْ تحدّثت.

[08:53] الآن إذا حفظنا هذا وحاولنا مجدّدًا، تسجيل الدخول، كما ترى نحصل فورًا على واجهتنا المحدَّثة — واجهتنا المحدَّثة — ويمكننا فعل الشيء ذاته لـ log out أيضًا: نلفّه في flushSync، وبهذه الطريقة إذا سجّلنا الدخول أو الخروج

[09:10] فكلا الأمرين يعمل فورًا.

[09:12] يمكننا أيضًا في أدوات المطوّر (dev tools) أن نلقي نظرة على سياقنا كذلك إذا احتجت إلى تتبّع شيء ما لتصحيحه. فإذا اخترت أيّ مسار (route) يمكنك رؤية أيّ سياق متوفّر في هذا المسار. ففيهنا لَدَينا سياقنا ولَدَينا

[09:27] user، وربّما يمكنك استخدام هذا للتصحيح (debugging) إذا كانت لَدَيك مشكلة من نوعٍ ما.

[09:33] كما ذكرنا، يمكننا أيضًا جعل السياق متوفّرًا لفرع معيّن فقط في شجرتنا. غير أنّ هذا ما سَنناقشه عندما نناقش دوالّ التحميل (load functions). لكن هذا يكفي بشأن السياق في الوقت الحالي. وبال

[09:47] طبع، كان هذا مجرّد مثال بسيط جدًا، لكن إذا شئت، ربّما في فيديو آخر، يمكننا في الواقع استخدام خدمة مصادقة (authentication service) حقيقية مدموجة مع سياقنا واستخدامها في مساراتنا وما إلى ذلك. غير أنّه، في هذا

[10:00] الفيديو، أردت فقط تغطية أكبر قدر ممكن من الميزات مستخدمًا هذه الأمثلة البسيطة.