شرح نظام Concurrent Mode القادم بقوة لمكتبة React

تم الإفصاح عن مشروع Concurrent Mode لأول مرة في مؤتمر ReactConf لعام 2018. فريق React.js قال بأن هذه الميزة المرتقبة ستساعد في حل المشاكل المرتبطة بالأداء وسرعة الإستجابة. كيف ذلك ؟ هذا ما سنكتشفه معا من خلال هذه التدوينة.

في الحقيقة مفهوم Concurrent Mode ليس جديدا في عالم تصميم البرمجيات، ولكنه بالنسبة لمكتبة React.js يفتح آفاقا جديدة وإمكانيات للتحسن أكثر على صعيد الأداء.

صحيح أن React.js تمكن من إنجاز تطبيقات ويب سريعة، ولكن في بعض الحالات نكون أمام مواقف نحاول فيها جعل واجهة المستخدم أكثر استجابة مما هي عليه، بالخصوص عند محاولة عرض (Render) مكونات معقدة (مثلا قوائم طويلة) ومكلفة لموارد أجهزة المستخدمين. والمشكلة هنا أن المستخدم لن يستطيع القيام بشيء آخر حتى ينتهي React.js من عرض ذلك المكون البطيء!

الأمر أشبه بصديقك وهو يتكلم دون ملل أو انقطاع بحيث لا يترك لك أي فرصة في الكلام 😄

هكذا عملية Rendering في React، تمنعك من القيام بأي شيء آخر في التطبيق حتى ولو كان ذلك الشيء أكثر أهمية!

نظام Concurrent Mode في مكتبة رياكت

ذكرت أعلاه أن فكرة Concurrency (أو التنافسية بين المهام والعمليات) في عالم البرمجيات ليست من ابتداع فريق رياكت، فعلى سبيل المثال تقوم المعالجات في الحواسيب بجدولة (Scheduling) عملياتها وترتيبها وفق أولوية وأهمية كل منها. نفس الشيء في مترجم جافاسكريبت، أو Compiler، ولكن عوض المعالج فإنه يعتمد على ما يعرف ب Single Thread أو الخيط الواحد. هذا الخيط يجب أن نستخدمه بحكمة وبشكل محسن حتى لا نقوم بإغراقه، وإلا فإنه سيتوقف ويتجمد في مكانه ويصبح من غير الممكن على المستخدم أن يتفاعل مع البرنامج حتى ينتهي خيط الجافسكريبت من عمله المتراكم.

نظام Concurrent Mode في مكتبة "رياكت" يسعى لمنع الوقوع في مثل هذه المشاكل ـ أو على الأقل المساعدة في تقليل فرص الوقوع فيها ـ عن طريق توفير خيوط صغيرة (Mini-Threads) خاصة به داخل Thread الرئيسي، وعن طريق مجدوله الخاص (Scheduler) يقوم بإدارة الأولويات وضمان استمرار تجاوب التطبيق مع تفاعلات المستخدم.

الأسبقية لما هو أكثر أهمية

إذن لكي نفهم ونستوعب مفهوم الوضع التنافسي في مكتبة React.js فعلينا أن نقوم بربطه بفكرة الأولية أو الأسبقية، بالإنجليزية Priorities.

الوضع التنافسي هي ترجمة من اجتهادي لمصطلح Concurrent Mode. إذا كانت لديكم ترجمات أخرى ترونها أدق وأصح فسأكون سعيدا إذا شاركتموها معي في التعليقات.

بدون Concurrent Mode عندما يقوم React.js بالبدء في عرض شيء ما فإنه لا يتوقف حتى ينتهي من عملية العرض.

مع Concurrent Mode تبقى مكتبة React.js مترقبة للأشياء الأخرى التي يجب أن تنجز، وإذا كان هناك شيء ما أكثر أهمية وذو أولوية عليا فإنه يتم إيقاف ما يتم عرضه (Pause Rendering) من أجل السماح أولا بإنهاء تلك المهام (Tasks) المُلحة.

هذه المهام يمكن أن تكون:

  • تفاعل للمستخدم مع المتصفح (النقر على زر، الكتابة في حقل نصي، عمل Scroll ...)
  • عرض أو تحديث آخر تسعى React.js للقيام به.
  • أي عملية أخرى تابعة لمكتبات أخرى أو أكواد ثانية.

ليس علينا أن نقوم بإخبار React.js بالعمليات التي يجب أن تعتبر مهمة، فهي ستقوم بمحاولة تخمين وتحديد ما هو مهم وما هو أقل أهمية.

تعرف خاصية العرض والتقديم القابل للإيقاف والتقطع في Concurrent Mode ب Interruptible Rendering وذلك بحسب توثيق المكتبة الرسمي، في مقابل آلية Blocking Rendering الموجودة حاليا.

تفعيل ميزة الوضع التنافسي في React.js

قبل التفعيل، يجب علينا أن نفهم جيدا بأن هذه الخاصية تجريبية وما تزال قيد التطوير من طرف فريق رياكت.

بصفة عامة ليس من الحكمة استخدام إصدارات تجريبية لأي مكتبة أو برنامج مهما كان في بيئة الإنتاج (Production)، لأنها غير مستقرة وإمكانيات وجود أخطاء وتعارضات أمر وارد جدا. من الواجب الإنتظار إلى حين الإعلان الرسمي عن إصدار React.js الرسمي الذي يضم هذه الميزة ليصبح بإمكاننا الإستعانة بها في وضع الإنتاج.

إلى ذلك الحين، لا بأس في التجربة محليا لفهم الفكرة وآلية العمل والإمكانيات التي يوفرها نظام Concurrent Mode.

الخطوة الأولى

في وقت كتابة هذا المنشور الإصدار الرسمي لمكتبة رياكت هو 16.12.0، وهو كما ذكرنا سابقا لا يدعم ميزة Concurrent Mode. إذن أول خطوة لنا من أجل إتمام عملية التفعيل هي تنزيل وإضافة إصدار React.js الذي يدعم الوضع التنافسي، وهو إصدار experimental أي الإصدار التجريبي.

npm install react@experimental react-dom@experimental

الخطوة الثانية

بعد تحميل الإصدارين التجريبيين لكل من react و react-dom قم بالتأكد من أن كل شيء ما زال يعمل كما ينبغي في التطبيق بدون أن تغير أو تلمس أي شيء في الكود.

تأكد من أن Console في المتصفح خال من الأخطاء.

الخطوة الثالثة.. تفعيل Concurrent Mode

إذا قمت بإنشاء المشروع بواسطة create-react-app لغرض القيام بهذه التجربة، فإنه سيكون لدينا ملف index.js شبيه بما يلي:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';

ReactDOM.render(<App />, document.getElementById('root'));

كل ما علينا فعله لتفعيل الوضع التنافسي هو تعويض السطر الأخير بما يلي:

ReactDOM.createRoot(document.getElementById('root')).render(<App />);

أي أن الملف index.js سيصبح على هذا الشكل:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';

ReactDOM.createRoot(document.getElementById('root')).render(<App />);

تغيير بسيط في API الخاص بتركيب المكون <App/> في شجرة DOM بواسطة ReactDOM، حيث ظهرت لدينا لأول مرة في مكتبة رياكت الوظيفة createRoot().

الخطوة الرابعة

قم بإعادة الخطوة 2 🙂 تأكد من أن كل شيء مازال يعمل بشكل عادي ولا وجود لأخطاء أو رسائل تحذيرية.

ماذا بعد التفعيل ؟

يوفر لنا Concurrent Mode عالما جديد من المزايا والخصائص الجديدة، ولعلكم سمعتم من قبل عن Suspense ؟ هذا واحد من أهم قطع الغيار في مشروع Concurrent Mode، بل هي الأهم بدون شك. لن أتحدث عنه في هذا المنشور لأنني سأخصص له موضوعا مستقلا ومفصلا سأبدأ في كتابته إن شاء الله بعد الإنتهاء من هذا المقال.

ما يجب معرفته الآن عن React Suspense أنه يسمح لمكتبة React.js بتأجيل أي تحديث على مستوى حالة معينة (State) حتى يحصل مكونها (Component) على كامل البيانات (Data) التي يحتاجاها.

وإذا كان Concurrent Mode يهتم ويعالج مسألة الأولويات، فإن Suspense يهتم بجلب البيانات (Data Fetching) وجعل المكونات تنتظر إلى حين توصلها بكل Data لتحديث نفسها.

في انتظار وصول تلك البيانات، سيقوم Suspense بعرض مكون fallback (عبارة عن Loader في غالب الأحيان)، أو يمكننا استخدام Hook جديد اسمه useTransition وهو كذلك ضمن المزايا التي ستأتي مع Concurrent Mode.

useTransition سيمكننا، عبر القيم التي يقوم بإرجاعها، من تأجيل تحديث واجهة المستخدم (بمعنى آخر تحديث State) إلى حين انتهاء المهلة التي نقوم بتمريرها له على شكل بارامتر.

function App() {
  const [startTransition, isPending] = useTransition({    timeoutMs: 3000  });  // ...

لنتخيل مثلا أننا في تطبيق ما نريد الدخول إلى صفحة البروفايل الخاصة بمستخدم معين، عوض النقر على زر الإنتقال وظهور Loader مباشرة لإخبارنا بأن تحميل البيانات جاري، يمكننا أن نطلب من React بعد النقر على الزر أو الرابط أن تنتظر مدة زمنية قبل الإنتقال للصفحة الجديدة. حينذاك ستكون البيانات قد جهزت بالفعل ولن تكون هناك حاجة لعرض Loader. هذا أفضل من ناحية تجربة الإستخدام UX بحسب رأي فريق رياكت الذي بنوه على دراسات وما يتعلمونه كل يوم من خلال المواقع والتطبيقات الضخمة التي يعملون عليها.

في السابق كان منح مثل هذه التجارب للمستخدمين أمرا صعبا على React.js، ولكن مع Concurrent Mode أصبح لدينا مجموعة من الأدوات مثل useTransition() و <Suspense/> لفعل ذلك بفعالية وأقل مجهود.

مثال

لنأخذ بسرعة المثال التالي الذي أخذته من توثيق رياك الرسمي:

return (
  <>
    <button
      disabled={isPending}
      onClick={() => {
        startTransition(() => {          const nextUserId = getNextId(resource.userId);          setResource(fetchProfileData(nextUserId));        });      }}
    >
      Next
    </button>
    {isPending ? ' Loading...' : null}    <ProfilePage resource={resource} />
  </>
);

نلاحظ أننا طلبنا من React ـ عن طريق الدالة startTransition التي يوفرها لنا useTransition() ـ بدء عملية الإنتقال نحو الصفحة الجديدة وذللك بطلب البيانات التي نحتاجها. إذا وصلت إلينا تلك البيانات في أقل من المدة الزمنية التي حددناها فإنه سيتم الإنتقال إلى الصفحة مباشرة دون انتظار إتمام تلك المدة. أما إذا احتاج الحصول على الداتا أكثر من الوقت الذي حددناه فإن React تقوم بالإنتقال للصفحة الجديدة على أي حال وتعود للمربع الصفر حيث تعرض Loader فيما تبقى من الوقت لإستلام البيانات.

القيمة isPending عبارة عن Boolean يخبرنا إن كانت عملية transition مازالت جارية أم لا.

يمكنكم تجربة الكود الكامل عن طريق رابط CodeSandbox التالي، وملاحظة كل ما قلناه أعلاه.

useTransition() يعمل بتناغم تام مع آلية React Suspense في طلب البيانات، ولا يفترض أن يعمل بمعزل عن ذلك الإطار.

في الختام

هكذا قدمنا الخطوط العريضة لمشروع Concurrent Mode لمكتبة رياكت، يمكنكم تجربته لاكتشاف المزيد من المقومات والمميزات التي يمنحها لمطوري React.js.

لا يوجد ـ إلى الوقت الحالي ـ معلومة عن موعد صدور وخروج هذا الإصدار بشكل رسمي، كل ما يمكننا القيام به حاليا كما قلت هو اختباره وهذا بحد ذاته يساعد الفريق المطور في الحصول على آراء المطورين حول العالم وتجاربهم المختلفة من أجل تحسين وإصلاح ما يمكن إصلاحه.

سيكون هناك مقال قادم ومفصل بإذن الله نشرح فيه تفاصيل مهمة عن مشروع React Suspense ودوره المرتقب في تجربة استخدام React، سواء بالنسبة للمطورين أو المستخدمين العاديين. هكذا سنرسم صورة أكبر لملامح المستقبل الواعد الذي تعدنا به هذه المكتبة.

إلى ذلك الحين، دعوني أرى انطباعاتكم عن هذا الموضوع وفائدته عليكم كمطوري رياكت 😉

شاركوا المقال مع أصدقائكم كذلك، هذا سيحفزني أكثر لتقديم المزيد في هذا الصدد 👍 🚀

مراجع مهمة