الحفاظ على نقاء المكوّنات

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

You will learn

  • ما هو النقاء وكيف يساعدك على تجنب الأخطاء (bugs)
  • كيفية الحفاظ على نقاء المكوّنات عن طريق إبقاء التغييرات خارج مرحلة التصيير (render phase)
  • كيفية استخدام الوضع الصارم (Strict Mode) للعثور على الأخطاء في مكوّناتك

النقاء: المكوّنات (components) كمعادلات رياضية

في علم الحاسب (وخاصة عالم البرمجة الوظيفية (functional programming)), الدالة النقية هي دالة بالخواص التالية:

  • تهتم بشؤونها. لا تغير أي كائنات أو متغيرات كانت موجودة قبل استدعائها.
  • نفس المدخلات تؤدي لنفس المخرجات بإعطاء نفس المدخلات ، يجب أن تُرجع الدالة النقية نفس النتيجة دائمًا.

قد تكون بالفعل على دراية بمثال واحد من الوظائف النقية: المعادلات في الرياضيات.

انظر معادلة الرياضيات هذه: y = 2x.

إذا x = 2 عندها y = 4. دائمًا.

إذا x = 3 عندها y = 6. دائمًا.

إذا x = 3, y لن تكون احيانًا 9 أو –1 أو 2.5 اعتمادًا على الوقت في اليوم أو حالة البورصة.

إذا y = 2x و x = 3، y ستكون دائمًا 6.

إذا قمنا بتحويل هذا إلى دالة JavaScript، فسيبدو كما يلي:

function double(number) {
return 2 * number;
}

في المثال أعلاه ، “double” هي دالة نقية. إذا مررت بـ “3” ، فستُرجع “6”. دائماً.

تم تصميم React حول هذا المفهوم. تفترض React أن كل مكّون تكتبه هو دالة نقية. هذا يعني أن مكوّنات React التي تكتبها يجب أن تُرجع دائمًا نفس JSX مع الأخذ في الاعتبار نفس المدخلات:

function Recipe({ drinkers }) {
  return (
    <ol>    
      <li>أغلي {drinkers} كوب ماء.</li>
      <li>أضف {drinkers} ملعقة شاي و {0.5 * drinkers} ملعقة توابل.</li>
      <li>أضف {0.5 * drinkers} كوب حليب للغلي وسكر للتذوق</li>
    </ol>
  );
}

export default function App() {
  return (
    <section>
      <h1>وصفة شاي متبل</h1>
      <h2>لاثنين</h2>
      <Recipe drinkers={2} />
      <h2>لتجمّع</h2>
      <Recipe drinkers={4} />
    </section>
  );
}

عند تمرير drinkers={2} إلى Recipe, ستعيد JSX تحتوي على 2 اكواب من الماء. دائمًا.

إذا قمت بتمرير drinkers={4}, ستعيد JSX تحتوي على 4 اكواب من الماء. دائمًا.

تمامًا مثل الصيغ الرياضية.

يمكنك التفكير في المكوّنات الخاصة بك كوصفات: إذا اتبعتها ولم تقم بإدخال مكوّنات جديدة أثناء عملية الطهي، ستحصل على نفس الطبق في كل مرة. هذا “الطبق” هو ال JSX الذي يقدمه المكوّن لReact للتصيير.

A tea recipe for x people: take x cups of water, add x spoons of tea and 0.5x spoons of spices, and 0.5x cups of milk

Illustrated by Rachel Lee Nabors

الآثار الجانبية: العواقب (غير المقصودة)

يجب أن تكون عملية التصيير في React دائمًا نقية. يجب أن تقوم المكوّنات فقط بـإرجاع JSX الخاص بهم، وعدم تغيير أي كائنات أو متغيرات كانت موجودة قبل عملية التصيير-فأن هذا سيجعلهم غير نقيين!

فيما يلي مكوّن يخالف هذه القاعدة:

let guest = 0;

function Cup() {
  // سيئ: تغيير متغير موجود بالفعل!
  guest = guest + 1;
  return <h2>كوب شاي للضيف رقم {guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup />
      <Cup />
      <Cup />
    </>
  );
}

يقوم هذا المكوّن بقراءة وكتابة متغير guest المعلن خارجه. هذا يعني أن استدعاء هذا المكوّن مرات متعددة سينتج JSX مختلف بل وأكثر من ذلك ، إذا قرأت المكوّنات الأخرى guest, سوف تنتج JSX مختلف , أيضًا ، اعتمادًا على متى تم تصييرها! وهذا لا يمكن توقعه

نعود إلى صيغتنا السابقة y = 2x, الآن حتى إذا كان x = 2, لا يمكننا الاعتماد على أن y = 4. قد تفشل اختباراتنا ، وقد يصبح المستخدمون مشوشين, وقد تسقط الطائرات من السماء - يمكنك رؤية كيف يمكن أن يؤدي ذلك إلى خلل مربك!

يمكنك إصلاح هذا العنصر عن طريق تمرير guest كخاصية:

function Cup({ guest }) {
  return <h2>كوب شاي للضيف رقم {guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup guest={1} />
      <Cup guest={2} />
      <Cup guest={3} />
    </>
  );
}

الآن يعتبر المكوّن الخاص بك نقيًا، حيث أن JSX الذي يُرجعه يعتمد فقط على خاصية guest.

بشكل عام، لا يجب عليك أن تتوقع أن يتم تصيير المكوّنات الخاصة بك بترتيب معين. لا يهم إذا قمت بطلب y = 2x قبل أو بعد y = 5x: ستتم حل كلا الصيغ بشكل مستقل عن بعضهما البعض. بنفس الطريقة، يجب على كل مكوّن “أن يفكر لنفسه” فقط، ولا يحاول التنسيق أو الاعتماد على المكوّنات الأخرى أثناء التصيير. التصيير مثل امتحان مدرسي: يجب على كل مكوّن حساب JSX بمفرده!

Deep Dive

اكتشاف الحسابات غير النقية باستخدام الوضع الصارم

على الرغم من أنه قد لا تكون قد استخدمت جميعها بعد, في React هناك ثلاثة أنواع من المدخلات التي يمكنك قراءتها أثناء التصيير: الخصائص, الحالة, و السياق. يجب عليك دائمًا معاملة هذه المدخلات على أنها للقراءة فقط.

عندما تريد تغيير شيء ما استجابة لإدخال المستخدم، يجب عليك تعيين حالة بدلاً من الكتابة في متغير. يجب ألا تقوم بتغيير المتغيرات أو الكائنات الموجودة مسبقًا أثناء تصيير المكوّن الخاص بك.

React يوفر “وضعًا صارمًا”(Strict Mode) يقوم فيه باستدعاء دالة كل مكوّن مرتين أثناء التطوير. من خلال استدعاء وظائف المكوّن مرتين، يساعد الوضع الصارم في العثور على المكوّنات التي تخالف هذه القواعد.

لاحظ كيف عرض المثال الأصلي “Guest #2”, “Guest #4”, و “Guest #6” بدلاً من “Guest #1”, “Guest #2”, و “Guest #3”. كانت الدالة الأصلية غير نقية، لذا تعطلت عند استدعاءها مرتين. ولكن الإصدار النقي المُصلح يعمل حتى إذا تم استدعاء الوظيفة مرتين. الدوال النقية تقوم بالحساب فقط، لذلك لن يتغير أي شيء عند استدعائها مرتين—تمامًا مثل استدعاء double(2) مرتين لن يتغير ما يتم إرجاعه، وحل y = 2x مرتين لن يغير ما هو y. نفس المدخلات، نفس المخرجات. دائمًا.

الوضع الصارم لا يؤثر في الإنتاج، لذلك لن يبطئ التطبيق لمستخدمينك. يمكنك الانضمام إلى الوضع الصارم, عن طريق تغليف المكّون الجذر(root component) الخاص بك في <React.StrictMode>. تفعل بعض الإطارات ذلك افتراضيًا.

التغيير المحلي: سر صغير لمكوّناتك

في المثال أعلاه، كانت المشكلة في أن المكوّن قام بتغيير متغير موجود مسبقًا أثناء التصيير. يُطلق عليها في كثير من الأحيان “طفرة” لجعلها تبدو أكثر رعبًا. الدوال النقية لا تغيّر المتغيرات خارج نطاق الدالة أو الكائنات التي تم إنشاؤها قبل الاستدعاء - هذا يجعلها غير نقية!

ومع ذلك, فمن المسموح تمامًا بتغيير المتغيرات والكائنات التي قمت بإنشائها فقط خلال التصيير في هذا المثال, قم بإنشاء [] مصفوفة, وعيينها إلى متغير cups, ثم ادفع (push) اثني عشر كوبًا فيها:

function Cup({ guest }) {
  return <h2>كوب شاي للضيف رقم {guest}</h2>;
}

export default function TeaGathering() {
  let cups = [];
  for (let i = 1; i <= 12; i++) {
    cups.push(<Cup key={i} guest={i} />);
  }
  return cups;
}

إذا تم إنشاء متغير cups او [] مصفوفة خارج دالة TeaGathering فستكون هذه مشكلة كبيرة! ستقوم بتغيير كائن موجود مسبقًا عند دفع العناصر في تلك المصفوفة.

ومع ذلك, فإنه يعد أمرًا صحيحًا لأنك قمت بإنشائهم خلال نفس التصيير, داخل TeaGathering. لن يعرف أي كود خارج TeaGathering ابدًا أن هذا حدث. يُطلق على هذا “التغيير المحلي”—هو مثل سر صغير لمكوّناتك.

اين يمكنك التسبب بآثار جانبية

على الرغم من أن البرمجة الوظيفية تعتمد بشدة على النقاء، إلا أنه في نقطة ما، في مكان ما ،يجب أن يتغير شيء. هذه هي النقطة من البرمجة! هذه التغييرات - تحديث الشاشة، بدء الرسوم المتحركة، تغيير البيانات - تسمى الآثار الجانبية إنها أشياء تحدث “على الجانب”, وليس خلال التصيير.

في React, تنتمي الآثار الجانبية عادةً داخل معالجات الأحداث(event handlers). معالجات الأحداث هي الدوال التي يقوم React بتشغيلها عندما تقوم بإجراء بعض الإجراءات—على سبيل المثال، عند النقر فوق زر. على الرغم من أن معالجات الأحداث يتم تعريفها داخل المكوّن الخاص بك، إلا أنها لا تعمل خلال التصيير! لذلك، فإن معالجات الأحداث لا يجب أن تكون نقية.

إذا استنفذت كل الخيارات الأخرى ولم تتمكن من العثور على معالج أحداث مناسب للآثار الجانبية، فيمكنك ربطها على الJSX المُرجَع الخاص بك باستدعاءuseEffect في مكوّنك. يخبر هذا React بتنفيذها لاحقًا، بعد التصيير، عندما يسمح بالآثار الجانبية. ومع ذلك، يجب أن يكون هذا النهج هو خيارك الأخير.

عندما يكون ذلك ممكنًا، حاول التعبير عن منطقك فقط من خلال التصيير. ستتفاجأ الى اي مدى يمكن لهذا أن يأخذك!

Deep Dive

لماذا يهتم React بالنقاء؟

يتطلب كتابة الدوال النقية بعض العادات والانضباط. ولكنه يفتح أيضًا فرصًا رائعة:

  • يمكن لمكوّناتك أن تعمل في بيئة مختلفة - على سبيل المثال، على الخادم! نظرًا لأنها تعيد نفس النتيجة لنفس المدخلات، يمكن لمكوّن واحد أن يخدم العديد من طلبات المستخدم.
  • يمكنك تحسين الأداء من خلال تخطي تصيير المكوّنات التي لم تتغير مدخلاتها. هذا آمن لأن الدوال النقية تعيد نفس النتائج دائمًا، لذلك فهي آمنة للتخزين المؤقت (cache).
  • إذا تغيرت بعض البيانات في منتصف تصيير شجرة مكوّنات عميقة، يمكن لـReact إعادة بدء التصيير دون إضاعة الوقت لإنهاء التصيير القديم. يجعل النقاء من الآمن التوقف عن الحساب في أي وقت.

كل الميزات الجديدة التي نقوم ببنائها في React تستفيد من النقاء. من جلب البيانات إلى الرسوم المتحركة إلى الأداء، الحفاظ على المكوّنات نقية يطلق العنان لقوة نموذج React.

Recap

  • يجب أن يكون المكوّن نقيًا، مما يعني:
    • يهتم بأمره الخاص. لا يجب أن يغير أي كائنات أو متغيرات كانت موجودة قبل التصيير.
    • نفس المدخلات تؤدي لنفس المخرجات. باعطاء نفس المدخلات، يجب على المكوّن أن يعيد دائمًا نفس JSX.
  • يمكن أن يحدث التصيير في أي وقت ، لذلك لا يجب أن تعتمد المكوّنات على تسلسل التصيير لبعضها البعض.
  • لا يجب تغيير أي من المدخلات التي تستخدمها المكوّنات الخاصة بك للتصيير. ويشمل ذلك الخصائص والحالة والسياق. لتحديث الشاشة ، استخدم, “set” state بدلاً من تغيير الكائنات الموجودة مسبقًا.
  • يجب ان تسعى للتعبير عن منطق المكوّن في الJSX الذي تعيده. عندما تحتاج إلى “تغيير الأشياء” ، عادةً ما تريد القيام بذلك في معالج الحدث(event listener). كخيار أخير ، يمكنك استخدام useEffect.
  • يتطلب كتابة الدوال النقية بعض الممارسة ، ولكنه يطلق العنان لقوة نموذج React.

Challenge 1 of 3:
إصلاح ساعة مكسورة

يحاول هذا المكوّن تعيين فئة(class) CSS لـ <h1> إلى "night" خلال الفترة من منتصف الليل إلى السادسة صباحًا، و "day" في جميع الأوقات الأخرى. ومع ذلك ، لا يعمل. هل يمكنك إصلاح هذا المكوّن؟

يمكنك التحقق مما إذا كان حلك يعمل عن طريق تغيير المنطقة الزمنية للحاسوب مؤقتًا. عندما يكون الوقت الحالي بين منتصف الليل والسادسة صباحًا ، يجب أن تكون الساعة قد عكست الوانها!

export default function Clock({ time }) {
  let hours = time.getHours();
  if (hours >= 0 && hours <= 6) {
    document.getElementById('time').className = 'night';
  } else {
    document.getElementById('time').className = 'day';
  }
  return (
    <h1 id="time">
      {time.toLocaleTimeString()}
    </h1>
  );
}