00:00:00مكونات خادم React. سواء أحببتها أم كرهتها. يبدو أن الكره هو الغالب هذه الأيام لكن
00:00:04قد يتغير ذلك قريباً مع دخول TanStack إلى اللعبة. هذا صحيح، لدينا الآن مكونات
00:00:08خادم TanStack وقد اتخذوا نهجاً مختلفاً تماماً عن Next.js. دعونا نلقي نظرة.
00:00:13سأبدأ بفقرة من منشور إعلانهم والتي أعتقد أنها ستريح
00:00:21الكثير من الناس. تقول الفقرة: "يعتقد معظم الناس الآن أن مكونات خادم React تعمل بطريقة
00:00:26تضع الخادم في المقام الأول. الخادم يمتلك الشجرة، و useClient يحدد الأجزاء التفاعلية، وتحدد اصطلاحات الإطار
00:00:31كيفية تجميع كل شيء معاً. هذا يحول مكونات خادم React من
00:00:35عنصر بدائي مفيد إلى شيء يجب أن يدور حوله تطبيقك بالكامل. لا نعتقد أنه يجب عليك
00:00:40تبني ذلك النموذج بالكامل مسبقاً فقط للحصول على القيمة من مكونات خادم React."
00:00:45بشكل أساسي، ما يقولونه هو أنهم لا يريدون اتباع مسار Next.js حيث تكون
00:00:48مكونات الخادم هي الافتراضية ثم تحتاج إلى توجيه useClient حيثما تريد الحصول على
00:00:52التفاعلية. بدلاً من ذلك، يريد TanStack التفكير في الأمر على أنه: ماذا لو كان بإمكانك استخدام مكونات خادم React
00:00:57بشكل دقيق كما تفعل عند جلب JSON على العميل؟ ومع وضع هذا الهدف في الاعتبار، دعونا نلقي نظرة على
00:01:01كيفية تنفيذهم لمكونات الخادم فعلياً، لأنني أحب حقاً الطريقة التي
00:01:06قاموا بها. ما لدي هنا هو تطبيق TanStack Start عادي، لذا كل شيء في الوقت الحالي
00:01:10سيكون مكوناً من جانب العميل، والشيء الوحيد الذي قمت به هو بعض خطوات التثبيت الصغيرة التي تحتاجها للحصول على
00:01:15مكونات الخادم تعمل، وهي ببساطة تثبيت بعض الحزم وتعديل
00:01:18ملف vconfig. هذا هو شكل الصفحة حالياً، لدينا مكون الترحيب هنا والذي
00:01:22يفترض أن يكون مكوناً من جانب العميل حالياً، وفي الكود هو حرفياً مجرد مكون React واحد
00:01:27ثم في الأسفل لدينا مسار TanStack عادي ونستخدم مكون الترحيب هنا. الآن دعونا
00:01:32نقل أنك تريد في مكون الترحيب القيام ببعض المنطق على الخادم. في حالتي، أريد الحصول على
00:01:36اسم مضيف نظام التشغيل، وأيضاً بعض متغيرات البيئة المتاحة فقط للخادم
00:01:40فقط لأريكم أن هذا يعمل بالفعل هناك. في الوقت الحالي إذا حاولت استخدام os.hostname
00:01:45فلن ينجح الأمر لأن هذه دالة Node وليست متاحة في المتصفح.
00:01:49ما نحتاجه إذن هو أخذ مكون الترحيب الخاص بنا وعرضه على الخادم، والخطوة الأولى
00:01:53للقيام بذلك في TanStack Start هي باستخدام دالة خادم بسيطة. كما ترون لدي
00:01:58واحدة هنا تسمى getGreeting وكل ما نفعله بداخلها هو استخدام دالة renderServer
00:02:01الجديدة، ونضع مكوننا بداخلها ونعيد مكون الخادم القابل للعرض
00:02:06الذي نحصل عليه. يمكنك التفكير في هذا ببساطة على أننا ننشئ طلب GET لمكوننا.
00:02:10بعد ذلك، كل ما نحتاجه هو ببساطة جلب المكون من دالة الخادم التي أنشأناها
00:02:14ويمكننا القيام بذلك داخل أداة التحميل (loader) على مسار هنا، فنحن فقط ننتظر await لـ getGreeting ثم
00:02:18نعيد ذلك أيضاً وهو لا يزال مكون خادم قابلاً للعرض. ثم يمكننا استخدامه داخل
00:02:23مسارنا هنا باستخدام useLoaderData ونستخدم المكون في الأسفل هكذا. وبذلك
00:02:27لدينا الآن أول مكون خادم TanStack. يمكنك رؤية أن os.hostname يعمل الآن كما أنه
00:02:32يجلب متغيرات البيئة المتاحة فقط على الخادم. الآن الشيء الذي
00:02:36أحببته حقاً في هذا التنفيذ هو أنك إذا لاحظت، الشيء الوحيد الجديد هنا هو دالة render
00:02:41ServerComponent. بقية هذا كان مجرد TanStack Start عادي. يمكنك استبدال هذا
00:02:46ببعض بيانات JSON بسيطة يتم إرجاعها وستجلبها بنفس الطريقة تماماً. أعتقد أيضاً
00:02:51أن هذا التنفيذ صريح جداً بشأن المكان الذي يعمل فيه كودك فعلياً.
00:02:55أنت تفعل كل ذلك داخل دالة خادم، لذا من الواضح تماماً أنه سيتم تشغيله على الخادم وأيضاً
00:02:59داخل renderServerComponent. في الواقع أعتقد أنه يمكنني تحسين كود العرض التوضيحي الخاص بي ببساطة
00:03:03بأخذ الدوال التي نقوم بتشغيلها هنا والتي أريد تشغيلها على الخادم، ووضعها
00:03:07داخل دالة الخادم ثم ببساطة تمرير تلك القيم إلى مكون الترحيب الخاص بي كخصائص (props)، بحيث
00:03:12الآن أصبح مكون الترحيب الخاص بي مجرد مكون أساسي يمكن استخدامه بالفعل على
00:03:16العميل أو الخادم. أنا أشغل كل المنطق الذي أريد تشغيله على الخادم داخل
00:03:21دالة الخادم الخاصة بي هنا. مرة أخرى، من الصريح جداً أنها ستعمل على الخادم. هذا
00:03:25يبدو حرفياً عكس المنطق المستخدم في Next.js وأنا أحبه تماماً.
00:03:30هذا يعني أنه يمكنني التفكير في React بطريقة طبيعية، فكل شيء يبدأ من العميل أولاً ثم إضافة مكونات الخادم
00:03:34فوقها عندما أريدها. ولكن ماذا لو أردت استخدام مكون من جانب العميل داخل مكون الخادم
00:03:38الخاص بي، أي متداخلاً داخل الشجرة؟ لنقل أنني أردت إضافة زر عداد هنا يقوم في كل مرة
00:03:43نضغط فيها بإضافة واحد إلى العداد. حسناً، إذا حاولت ببساطة إضافة هذا إلى مكون الخادم الخاص بي هنا مع
00:03:47onClick ثم أيضاً استدعاء useState، يمكنك رؤية أنه ينكسر تماماً. يقول إن useState ليست
00:03:52دالة أو أن قيمة إرجاعها ليست قابلة للتكرار، وذلك لأن greeting يتم استخدامه كمكون
00:03:56خادم. لا يمكننا استخدام وظائف العميل هذه. لإصلاح هذا، لديك خياران، والخيار الثاني
00:04:01هو بالتأكيد الأفضل للاستخدام، لكن الخيار الأول سيشعر به المألوف لأولئك
00:04:05منكم الذين استخدموا Next.js. يمكننا ببساطة نقل ذلك المنطق إلى مكونه الخاص ثم استخدام
00:04:10توجيه useClient. هذا يعمل في مكونات خادم TanStack Start، يمكننا ببساطة استخدام
00:04:14المكون داخل مكون الخادم الآن، وكما ترون، كل شيء يعمل بشكل جيد. الجانب السلبي
00:04:18لهذا النهج هو أن لدينا الآن مكون خادم يتحكم في عرض
00:04:22مكون عميل، وهذا يمكن أن يبدأ في أن يصبح فوضوياً قليلاً، أي إذا أردت معرفة مكان
00:04:28مكون العداد الخاص بي في الشجرة، سأذهب إلى مساري هنا وأرى أن لدينا مكون خادم الترحيب
00:04:32ثم أعود إلى مكون خادم الترحيب وبداخله أرى
00:04:37أن لدينا مكون عميل، وهذه الفوضى يمكن أن تتراكم حقاً وتجعل حدود
00:04:42الخادم والعميل غير واضحة قليلاً. لن يرضى TanStack بذلك. لقد سألوا
00:04:47أنفسهم: ماذا لو لم يحتج الخادم إلى اتخاذ قرار بشأن كل جزء من واجهة المستخدم الخاص بالعميل على الإطلاق؟
00:04:51وهذا قادهم إلى إنشاء شيء جديد تماماً - المكونات المركبة (Composite Components). لاستخدام أحدها، أول
00:04:56شيء سأفعله هو إزالة مكون العميل من مكون الخادم الخاص بي وسأستبدله ببساطة
00:05:00بأي أبناء (children) لمكون الترحيب الخاص بي. بعد ذلك، نحتاج أيضاً إلى تغيير ما نرجعه من
00:05:05دالة الخادم الخاصة بنا هنا. بدلاً من إرجاع مكون خادم قابل للعرض، نحتاج إلى إرجاع
00:05:09ما يسمى بمصدر مركب (composite source). للقيام بذلك، يمكننا استخدام مساعد مكون خادم TanStack الثاني
00:05:14دالة createCompositeComponent. هنا نحن نقوم ببساطة ببناء مكون
00:05:18حيث تعتبر الخصائص (props) هنا هي الفتحات (slots). أنا أستخدم فقط فتحة أبناء (children) بسيطة لذا
00:05:22سوف تمرر أي شيء نضعه كابن لمكوني المركب إلى props.children هذه التي
00:05:27أمررها إلى مكون الترحيب الذي كان لدينا للتو. مع ذلك، مرة أخرى، ما نحتاجه هو
00:05:31ببساطة جلب المكون المركب الخاص بنا من دالة الخادم الخاصة بنا، ونحن نفعل ذلك بنفس الطريقة في
00:05:36أداة التحميل (loader) هنا. ترون أنني أعيد تسمية المصدر (source) إلى greeting ثم أقوم بتحميله باستخدام useLoaderData.
00:05:41الفرق الوحيد هنا هو أننا لا نستطيع استخدام هذا كمكون. ترون أنه يرمي
00:05:45خطأ هنا. لعرض مكون مركب فعلياً نحتاج إلى استخدام مساعد المكون المركب
00:05:49الذي نحصل عليه من مكونات خادم TanStack، وكمصدر (source) نمرر
00:05:53المكون المركب الذي نجلبه من دالة الخادم التي أنشأناها سابقاً.
00:05:57بذلك، أصبح مكون الخادم الخاص بي يعرض كما كان من قبل وأنا أمرر أيضاً
00:06:01العداد كأبناء لهذا المكون المركب، وهذا يمر إلى الترحيب حيث
00:06:05أعددناه هنا، لذا فهو يمرره إلى props.children، فكل شيء يعمل الآن
00:06:10بشكل جيد. يمكنني أيضاً الذهاب إلى مكون العداد الخاص بي وإزالة التوجيه الذي كان لدينا
00:06:14هنا لأنه لم يعد مطلوباً لأنه يعرف أن هذا سيكون مكون عميل لأنه داخل مكون
00:06:18مركب. يتم استخدامه كفتحة (slot). الآن قد يبدو أننا حصلنا على نفس النتيجة تماماً
00:06:23ولكن بمجهود أكبر من مجرد استخدام توجيه useClient، لكن القوة تأتي حقاً من
00:06:27تجربة المطور وهي تحول قليل عن نموذج useClient. بدلاً من جعل
00:06:32خادمنا يقرر أين يتم عرض مكونات العميل الخاصة بنا مثلما عندما كان لدينا مكون العداد داخل
00:06:36مكون الخادم نفسه، بدلاً من ذلك ما نفعله بالمكونات المركبة هو القول: مرحباً،
00:06:40ستكون هناك فتحة هنا، سنعرض مكون عميل، لكن مكون الخادم
00:06:44نفسه ليس لديه أي فكرة عما سيكون عليه ذلك. نحن نضيف ذلك لاحقاً في كود العميل الخاص بنا
00:06:48لذا نحن نتعامل مع جميع المكونات المعتمدة على العميل داخل كود العميل نفسه. هذه مجرد البداية أيضاً
00:06:53إذا ألقينا نظرة على صفحة أكثر تعقيداً مثل صفحة المنشور هذه حيث يتم عرض هذا المنشور على الخادم
00:06:58هناك مشكلتان أريد حلهما. الأولى هي أنني أريد إضافة بعض الإجراءات مثل
00:07:03الإعجاب بالمنشور ومتابعة المؤلف ولكن أريد إضافتها فوق العنوان هنا وأريد
00:07:08استخدام مكون عميل. في الوقت الحالي أنا أستخدم نمط فتحة الأبناء (children slot) هذا، وهذا يعني إذا
00:07:12أضفت إجراءات المنشور الخاصة بي هنا في الأسفل فستذهب فقط إلى حيث توجد التعليقات لأن هذه هي الطريقة التي
00:07:17أعددنا بها المكون، لذا أريد طريقة لأخبر مكون الخادم الخاص بي أين يضع مكونات عميل
00:07:22محددة. ثم لدينا مشكلة ثانية وهي إذا كان لدي زر متابعة المؤلف. في الوقت الحالي، هذه
00:07:27صفحة المنشور ليس لديها أي فكرة عن هوية مؤلف المنشور. لقد قمنا بالفعل بنقل كل ذلك المنطق إلى
00:07:32مكون الخادم نفسه. إذا أردت الحصول على المؤلف في مكون عميل في الأسفل، فسيتعين علي
00:07:37جلب JSON الخاص بالمنشور والحصول على المؤلف بهذه الطريقة، وهذا ليس نمطاً رائعاً، فسنجلب البيانات مرتين.
00:07:42لحسن حظنا، TanStack لديه بالفعل نوعان آخران من الفتحات (slots)
00:07:46يمكننا استخدامهما في المكون المركب إلى جانب فتحة الأبناء (children) هذه، والأولى ستكون
00:07:50خصائص العرض (render props). هذه ببساطة أي خاصية عبارة عن دالة تعيد عنصر React، لذا
00:07:56يمكن تسمية هذا بأي شيء، لا يجب أن يسمى renderActions، وهنا أقول فقط ما هي
00:07:59البيانات التي أريد من مكون الخادم تمريرها، وهي معرف المنشور (post id) ومعرف المؤلف (author id).
00:08:04الآن كل ما نحتاجه في مكوننا المركب هو ببساطة استخدام هذه الدالة التي نمررها
00:08:08كخاصية أينما نريد أن يكون المكون الذي سيتم عرضه في النهاية.
00:08:12في حالتي أريد أن يكون ذلك تحت رأس البطاقة، لذا يمكنني استدعاؤها بـ props.render
00:08:16Actions، يمكننا استخدام خيار اختياري لذا إذا لم يتم تمريرها فلن تنكسر، فقط لن
00:08:20يتم عرضها، ثم يمكننا أيضاً تمرير المعلومات التي نريدها من مكون الخادم
00:08:24إلى مكون العميل الخاص بنا. بعد ذلك سيقبل مكوننا المركب خاصية
00:08:28renderActions التي أنشأناها للتو، وبالنسبة للقيمة نمرر ببساطة دالة تحتوي على معرف المنشور
00:08:32ومعرف المؤلف كوسيطة والتي سيقوم الخادم بملئها، ثم نعرض ببساطة مكون
00:08:36إجراءات المنشور الخاص بالعميل ويمكننا تمرير تلك البيانات كخصائص (props). والآن لدي زر
00:08:41هنا حيث يمكنني الإعجاب ونسخ رابط المنشور وأيضاً النقر على متابعة المؤلف هنا حيث
00:08:45يكون على دراية باسم المؤلف على الرغم من حقيقة أنني لم أقم بجلب ذلك في هذه الصفحة أبداً.
00:08:49أنا أجلبها فقط في مكون الخادم، ومكون الخادم يمرر تلك البيانات إلى
00:08:53مكون العميل من أجلي. الآن قد تعتقد أن هذا يكسر المنطق الذي كان لدينا من قبل حيث قلنا
00:08:57إننا لا نريد لأي مكونات خادم أن تكون مسؤولة عن عرض مكونات العميل، ولكنه لا يكسرها، وذلك
00:09:01لأن الفتحات (slots) معتمة (opaque). مكون الخادم هنا ليس لديه أي فكرة عما بداخل
00:09:06هذا، هو يعرف فقط أن شيئاً ما يذهب هنا وأنه يحتاج إلى تمرير هذه القيم، والتي في
00:09:10هذه الحالة هي معرف المنشور ومعرف المؤلف. هذه الدالة لا تعمل على الخادم، بدلاً من ذلك
00:09:15يرى الخادم ببساطة أنه يحتاج إلى تمرير البيانات، ثم في عميلنا هذا هو الوقت الذي
00:09:19يتم فيه تشغيل الدالة فعلياً وعرض المكون. نفس الشيء تماماً ينطبق أيضاً على
00:09:23نوع فتحتنا الثالث وهو خصائص المكون (component props)، هذا النوع في الواقع أبسط قليلاً من
00:09:28خصائص العرض (render props)، كل ما نفعله بدلاً من الحصول على دالة تعيد مكون العميل الخاص بنا
00:09:33هو أننا نمرر ببساطة مكون العميل كخاصية (prop) بحد ذاته، ثم في مكوننا المركب
00:09:38في الأعلى نحن نقول إننا نريد قبول خاصية وهي عبارة عن مكون React لديه
00:09:42خصائص معرف المنشور ومعرف المؤلف، ثم يمكننا استخدام هذا داخل المكون نفسه. يمكنك التفكير في
00:09:47خصائص المكون على أنها عنصر نائب (placeholder)، مكون الخادم يعرف أنه سيكون هناك مكون
00:09:51هناك يحتاج إلى بعض البيانات في حالتنا معرف المنشور ومعرف المؤلف، لكنه لا يهتم حقاً بما هو ذلك
00:09:56المكون طالما أنه يقبل تلك الخصائص، لذا قمت بتغيير مكون إجراءات المنشور الخاص بي في الأسفل إلى
00:10:01واحد آخر قمت بإنشائه يسمى fakePostActions، ثم عندما نحفظ ذلك يمكنك رؤية أن هذا
00:10:05سيظل يعرض لأنه العميل هو المسؤول عن عرض هذا المكون
00:10:10فقط الخادم هو الذي يوفر البيانات. بالنظر إلى الوثائق، لا يبدو أن هناك
00:10:14أي فرق حقيقي في النهج الذي تتخذه سواء اخترت خصائص المكون أو خصائص العرض
00:10:18قد يعود الأمر إلى التفضيل فقط. الفرق الوحيد الذي يمكنني رؤيته هو أنه ربما تريد
00:10:22تعديل البيانات التي تحصل عليها من الخادم، لذا في هذه الحالة يمكننا فعل ما نريد بـ
00:10:26معرف المنشور ومعرف المؤلف لأنها مجرد دالة، ومن ثم يمكننا تمرير ذلك إلى مكوننا
00:10:31بينما إذا كنت تستخدم خصائص المكون فإنك تمرر فقط المكون نفسه والخادم
00:10:36يتولى تمرير الخصائص. هذه هي أساسيات مكونات خادم TanStack ولكن لا يزال هناك
00:10:40الكثير مما يعجبني، على سبيل المثال إذا أردت أن يتم عرض معظم صفحتك على الخادم
00:10:44ربما لديك مكون رأس، ومكون محتوى، ومكون تذييل وأردتها جميعاً أن تُعرض على
00:10:49الخادم، لا يتعين عليك حزمها جميعاً في دالة renderServerComponent واحدة، يمكنك في الواقع
00:10:53استخدام Promise.all وتقسيمها إلى ثلاث دوال مختلفة ثم ببساطة إرجاعها ككائن
00:10:58من دالة خادم واحدة. ولكن ماذا لو استغرق أحد هذه المكونات وقتاً طويلاً للتحميل؟ هذا يعني أن
00:11:03دالة الخادم بأكملها وبالتالي الصفحة بأكملها ستتأخر. حسناً، لا تقلق بشأن ذلك أيضاً،
00:11:07ما يمكننا فعله في الواقع هو بدلاً من انتظار دالة renderServerComponent، يمكننا في الواقع
00:11:12إرجاع الوعد (promise) الذي تنشئه، ثم على العميل يمكننا الاستفادة من خطاف use و
00:11:16حدود التعليق (suspense boundaries) لتحميل الهياكل العظمية (skeletons)، لذا سيتم تحميل مكونات الخادم فقط عندما تكون جاهزة.
00:11:21أنا أحب حقاً النهج الذي اتبعه TanStack هنا، فهو لا يبدو تدخلياً، ولست مجبراً
00:11:25على تبنيه، ويمكنني تبنيه بدون أي حلول غريبة، بالإضافة إلى أنه عندما أذهب فعلياً لاستخدامه
00:11:31تكون مكونات الخادم نفسها في الواقع ثلاث دوال جديدة فقط، وبقية الأمر هو مجرد
00:11:36دوال خادم TanStack Start بسيطة، شيء كنت أستخدمه بالفعل، وهو بسيط مثل جلب
00:11:41البيانات. هذا يعني أيضاً أنه يتكامل بشكل جيد مع أدوات مثل TanStack Query، وهو شيء سأقوم به
00:11:45بالتأكيد، كما أنه يجعل أشياء مثل التخزين المؤقت (caching) أبسط، إذا أردت، يمكنك حرفياً
00:11:49مجرد تخزين استجابة طلب GET مؤقتاً على شبكة توصيل المحتوى (CDN) الخاصة بك. سأستكشف بالتأكيد المزيد منها
00:11:54لذا أخبرني في التعليقات أدناه برأيك فيها وإذا كنت ترغب في رؤية المزيد من الفيديوهات عنها
00:11:59حسناً، نعم اشترك وكما هو الحال دائماً أراكم في الفيديو القادم.