الفرق بين المراجعتين ل"React/hooks reference"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
(إنشاء الصفحة)
 
(إضافة كامل الصفحة)
سطر 1: سطر 1:
 
''الخطافات هي إضافة جديدة إلى الإصدار 16.8 في React، إذ تسمح لك باستعمال ميزة الحالة وميزات React الأخرى دون كتابة أي صنف.''
 
''الخطافات هي إضافة جديدة إلى الإصدار 16.8 في React، إذ تسمح لك باستعمال ميزة الحالة وميزات React الأخرى دون كتابة أي صنف.''
  
تشرح هذه الصفحة الواجهات البرمجية للخطافات المضمَّنة في React.
+
تشرح هذه الصفحة الواجهات البرمجية للخطافات المضمَّنة في [[React]].
  
إن كان موضوع الخطافات جديدًا بالنسبة لك، فيرجى الرجوع إلى صفحة [[React/hooks intro|مدخل إلى الخطافات]] وقراءتها أولًا. قد تجد أيضًا الكثير من المعلومات المفيدة في قسم [[React/hooks faq|الأسئلة الشائعة]].
+
إن كان موضوع الخطافات جديدًا بالنسبة لك، فيرجى الرجوع إلى صفحة "[[React/hooks intro|مدخل إلى الخطافات]]" وقراءتها أولًا. قد تجد أيضًا الكثير من المعلومات المفيدة في قسم [[React/hooks faq|الأسئلة الشائعة]].
  
 
== الخطافات الأساسية ==
 
== الخطافات الأساسية ==
سطر 10: سطر 10:
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
const [state, setState] = useState(initialState);
 
const [state, setState] = useState(initialState);
</syntaxhighlight>يعيد قيمةً ذات حالة، ودالةً لتحديث هذه القيمة.
+
</syntaxhighlight>يعيد هذا الخطاف قيمةً ذات حالة، ودالةً لتحديث هذه القيمة.
  
 
أثناء عملية التصيير الأولية، الحالة المعادة (<code>state</code>)  هي نفسها القيمة المُمرَّرة كأول وسيط (<code>initialState</code>).
 
أثناء عملية التصيير الأولية، الحالة المعادة (<code>state</code>)  هي نفسها القيمة المُمرَّرة كأول وسيط (<code>initialState</code>).
  
تُستعمَل الدالة setState لتحديث الحالة، إذ تقبل قيمة جديدة للحالة وتدرج في الطابور عملية إعادة تصيير لمكون.<syntaxhighlight lang="javascript">
+
تُستعمَل الدالة <code>setState</code> لتحديث الحالة، إذ تقبل قيمةً جديدةً للحالة وتدرج في الطابور عملية إعادة تصيير لمكون.<syntaxhighlight lang="javascript">
 
setState(newState);
 
setState(newState);
  
</syntaxhighlight>أثناء عمليات إعادة التصيير اللاحقة، القيمة الأولى التي يعيدها الخطاف useState ستبقى دومًا أحدث حالة بعد تطبيق التحديثات.
+
</syntaxhighlight>أثناء عمليات إعادة التصيير اللاحقة، القيمة الأولى التي يعيدها الخطاف <code>useState</code> ستبقى دومًا أحدث حالة بعد تطبيق التحديثات.
  
=== تحديثات عبر تمرير دالة ===
+
==== تحديثات عبر تمرير دالة ====
إن حُسبَت الحالة الجديدة باستعمال الحالة السابقة، فيمكنك تمرير دالة إلى <code>setState</code>.ستستقبل الدالة القيمة السابقة، وتعيد القيمة المحدَّثة. إليك مثالٌ عن مكون عداد يستعمل كلا الشكلين للخطاف setState:<syntaxhighlight lang="javascript">
+
إن حُسبَت الحالة الجديدة باستعمال الحالة السابقة، فيمكنك تمرير دالة إلى <code>setState</code>.ستستقبل الدالة القيمة السابقة، وتعيد القيمة المحدَّثة. إليك مثالٌ عن مكون عداد يستعمل كلا الشكلين للخطاف <code>setState</code>:<syntaxhighlight lang="javascript">
 
function Counter({initialCount}) {
 
function Counter({initialCount}) {
 
   const [count, setCount] = useState(initialCount);
 
   const [count, setCount] = useState(initialCount);
سطر 32: سطر 32:
 
   );
 
   );
 
}
 
}
</syntaxhighlight>يستعمل الزر "+" والزر "-" الشكل الدالِّي (functional form) لأن القيمة المحدَّثة تعتمد على القيمة السابقة. ولكن الزر "Reset" يستعمل الشكل الاعتيادي لأنه يضبط العداد إلى القيمة 0 دومًا.
+
</syntaxhighlight>يستعمل الزر "+" والزر "-" الشكل الدالِّي (functional form) لأنَّ القيمة المحدَّثة تعتمد على القيمة السابقة. ولكن الزر "Reset" يستعمل الشكل الاعتيادي لأنَّه يضبط العداد إلى القيمة 0 دومًا.
  
ملاحظة: خلافًا للتابع setState في مكونات الأصناف، الخطاف useState لا يدمج كائنات التحديث (update objects). يمكنك تكرار هذا السلوك عبر دمج شكل الدالة المحدِّثة مع [[JavaScript/Spread Operator|معامل النشر للكائن]]:<syntaxhighlight lang="javascript">
+
'''ملاحظة''': خلافًا للتابع <code>setState</code> في مكونات الأصناف، الخطاف <code>useState</code> لا يدمج كائنات التحديث (update objects). يمكنك تكرار هذا السلوك عبر دمج شكل الدالة المحدِّثة مع [[JavaScript/Spread Operator|معامل النشر للكائن]]:<syntaxhighlight lang="javascript">
 
setState(prevState => {
 
setState(prevState => {
 
   // Object.assign would also work
 
   // Object.assign would also work
سطر 40: سطر 40:
 
});
 
});
  
</syntaxhighlight>هنالك خيار آخر وهو استعمال الخطاف <code>useReducer</code> الذي يعد مناسبًا لإدارة كائنات حالة تحوي عدة قيمة فرعية.
+
</syntaxhighlight>هنالك خيار آخر وهو استعمال الخطاف <code>useReducer</code> الذي يعدُّ مناسبًا لإدارة كائنات حالة تحوي عدة قيم فرعية.
  
=== الحالة الأولية الكسولة ===
+
==== الحالة الأولية الكسولة ====
الوسيط initialState هو الحالة المستعملة أثناء عملية التصيير الأولى (initial render). في عمليات التصيير اللاحقة، سيهمل هذا الوسيط. إن كانت الحالة الأولية هي ناتج عملية حساب معقدة تؤثر على الأداء، فيمكنك أن تمرر دالة عوضًا عن ذلك والتي ستُنفَّذ مرةً واحدةً في أول عملية تصيير:<syntaxhighlight lang="javascript">
+
الوسيط <code>initialState</code> هو الحالة المستعملة أثناء عملية التصيير الأولى (initial render). في عمليات التصيير اللاحقة، سيُهمَل هذا الوسيط. إن كانت الحالة الأولية هي ناتج عملية حساب معقدة تؤثر على الأداء، فيمكنك أن تمرِّر دالةً تُنفَّذ مرةً واحدةً في أول عملية تصيير عوضًا عن ذلك:<syntaxhighlight lang="javascript">
 
const [state, setState] = useState(() => {
 
const [state, setState] = useState(() => {
 
   const initialState = someExpensiveComputation(props);
 
   const initialState = someExpensiveComputation(props);
سطر 50: سطر 50:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== عدم تغير الحالة عن إجراء تحديث عليها ===
+
==== عدم تغير الحالة عن إجراء تحديث عليها ====
إن حدَّث خطاف حالة وكانت القيمة المحدَّثة نفسَ قيمة الحالة الحالية، فلن تتكبد React عنا تصيير الابن أو تنفيذ التأثيرات. (تستعمل React الخوارزمية Object.is لإجراء عملية الموازنة.)
+
إن حدَّث خطاف حالة وكانت القيمة المحدَّثة نفسَ قيمة الحالة الحالية، فلن تتكبد [[React]] عناء تصيير الابن أو تنفيذ التأثيرات. (تستعمل [[React]] [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#Description الخوارزمية Object.is] لإجراء عملية الموازنة.)
  
 
=== <code>useEffect</code> ===
 
=== <code>useEffect</code> ===
سطر 57: سطر 57:
 
useEffect(didUpdate);
 
useEffect(didUpdate);
  
</syntaxhighlight>يقبل هذا الخطاف دالةً تحوي أمرًا يكون غالبًا شيفرة ذات تأثير.
+
</syntaxhighlight>يقبل هذا الخطاف دالةً تحوي أمرًا يكون غالبًا شيفرةً ذات تأثير.
  
التعديلات، والاشتراكات، والمؤقتات، والسجلات، والتسجيل (logging)، والتأثيرات الجانبية الأخرى غير مسموح بها داخل الجسم الرئيسي لمكون دالة (يشار إليه على أنَّه مرحلة تصيير React [أي render phase]). سيؤدي فعل ذلك إلى حصول أخطاءٍ مربكة مع تناقضات في واجهة المستخدم.
+
التعديلات، والاشتراكات، والمؤقتات، والسجلات، والتسجيل (logging)، والتأثيرات الجانبية الأخرى غير مسموحٍ بها داخل الجسم الرئيسي لمكون دالة (يشار إليه على أنَّه مرحلة تصيير [[React]] [أي render phase]). سيؤدي فعل ذلك إلى حصول أخطاءٍ مربكة مع تناقضات في واجهة المستخدم.
  
عوضًا عن ذلك، استعمل الخطاف useEffect. الدالة المُمرَّر إليه ستُنفَّذ بعد الانتهاء من التصيير على الشاشة. فكر في التأثيرات وكأنها مخرج هروب (escape hatch) من عالم React الوظيفي البحت إلى العالم الأمري.
+
عوضًا عن ذلك، استعمل الخطاف <code>useEffect</code>. الدالة المُمرَّر إليه ستُنفَّذ بعد الانتهاء من التصيير على الشاشة. فكر في التأثيرات وكأنَّها مخرج هروب (escape hatch) من عالم [[React]] الوظيفي البحت إلى العالم الأمري.
  
 
افتراضيًّا، تُنفَّذ التأثيرات بعد كل كل عملية تصيير مكتملة، ولكن يمكنك اختيار تنفيذها فقط عند تغير قيم محدَّدة.
 
افتراضيًّا، تُنفَّذ التأثيرات بعد كل كل عملية تصيير مكتملة، ولكن يمكنك اختيار تنفيذها فقط عند تغير قيم محدَّدة.
  
=== تنظيف تأثير ===
+
==== تنظيف تأثير ====
تنشئ التأثيرات غالبًا موارد تحتاج للتنظيف قبل أن يغادر المكون الشاشة مثل معرِّف اشتراك أو مؤقت. لفعل ذلك، قد تعيد الدالة المُمرَّرة إلى الخطاف useEffect دالةً تجري عملية التنظيف. على سبيل المثال، لإنشاء اشتراك:<syntaxhighlight lang="javascript">
+
تنشئ التأثيرات غالبًا موارد تحتاج للتنظيف قبل أن يغادر المكون الشاشة مثل معرِّف اشتراك أو مُؤقِّت. لفعل ذلك، قد تعيد الدالة المُمرَّرة إلى الخطاف <code>useEffect</code> دالةً تجري عملية التنظيف. على سبيل المثال، يمكن إنشاء اشتراك بالشكل التالي:<syntaxhighlight lang="javascript">
 
useEffect(() => {
 
useEffect(() => {
 
   const subscription = props.source.subscribe();
 
   const subscription = props.source.subscribe();
سطر 74: سطر 74:
 
   };
 
   };
 
});
 
});
</syntaxhighlight>تُنفَّذ دالة التنظيف قبل حذف المكون من واجهة المستخدم لمنع حدوث تسريب في الذاكرة. أضف إلى ذلك أنَّه إن صيَّر مكوِّنٌ عدة مرات (كما تفعل عادةً)، فسيُنظَّف التأثير السابق قبل تنفيذ التأثير اللاحق. في مثالنا، هذا يعني أنَّه يُنشَأ اشتراك جديد في كل تحديث. لتجنب تنفيذ التأثير عند كل عملية تحديث، ارجع إلى القسم التالي.
+
</syntaxhighlight>تُنفَّذ دالة التنظيف قبل حذف المكون من واجهة المستخدم لمنع حدوث تسريب في الذاكرة. أضف إلى ذلك أنَّه إن صُيَّر مكوِّنٌ عدَّة مرات (كما تفعل عادةً)، فسيُنظَّف التأثير السابق قبل تنفيذ التأثير اللاحق. في مثالنا، هذا يعني أنَّه يُنشَأ اشتراك جديد في كل تحديث. لتجنب تنفيذ التأثير عند كل عملية تحديث، ارجع إلى القسم التالي.
  
=== توقيت التأثيرات ===
+
==== توقيت التأثيرات ====
خلافًا للتابعين componentDidMount و componentDidUpdate، تُنفَّذ الدالة المُمرَّرة إلى الخطاف useEffect بعد التخطيط والرسم أثناء حدث مؤجل (deferred event). هذا يجعلها مناسبة للاستعمال مع العديد من التأثيرات الجانبية الشائعة مثل ضبط الاشتراكات ومعالجات الحدث لأن أغلب أنواع العمل لا يجب أن يحجز المتصفح عن تحديث الشاشة.
+
خلافًا للتابعين <code>componentDidMount</code> و <code>componentDidUpdate</code>، تُنفَّذ الدالة المُمرَّرة إلى الخطاف <code>useEffect</code> بعد التخطيط والرسم أثناء حدث مؤجَّل (deferred event). هذا يجعلها مناسبة للاستعمال مع العديد من التأثيرات الجانبية الشائعة مثل ضبط الاشتراكات ومعالجات الحدث لأنَّ أغلب أنواع العمل لا يجب أن يحجز المتصفح عن تحديث الشاشة.
  
على أية حال، لا يمكن تأجيل جميع التأثيرات. على سبيل المثال، التعديلات التي تجرى على DOM والظاهرة للمستخدم يجب أن تُنفَّذ بشكل متزامن قبل تنفيذ عملية الرسم التالية، لذا لا يلاحظ المستخدم تناقضات بصرية. (الفارق من ناحية النظرية مشابهٌ للفارق بين مستمعي حدث نشطين مقابل مستمعي حدث خاملين.) توفر React من أجل هذه الأنواع من التأثيرات خطافًا إضافيًّا يدعى useLayoutEffect. يملك هذا الخطاف نفس التوقيع الذي يملكه الخطاف useEffect. الاختلاف الوحيد بينهما هو وقت التنفيذ.
+
على أية حال، لا يمكن تأجيل جميع التأثيرات. على سبيل المثال، يجب أن تُنفَّذ التعديلات التي تجرَى على DOM والظاهرة للمستخدم بشكل متزامن قبل تنفيذ عملية الرسم التالية، لذا لا يلاحظ المستخدم أية تناقضات بصرية. (الفارق من ناحية النظرية مشابهٌ للفارق بين مستمعي حدث نشطين مقابل مستمعي حدث خاملين.) توفر [[React]] من أجل هذه الأنواع من التأثيرات خطافًا إضافيًّا يدعى <code>useLayoutEffect</code>. يملك هذا الخطاف نفس التوقيع الذي يملكه الخطاف <code>useEffect</code>. الاختلاف الوحيد بينهما هو وقت التنفيذ.
  
رغم أن الخطاف useEffect مؤجل لبعد انتهاء المتصفح من الرسم، فإن تنفيذه قبل أية عمليات تصيير أخرى أمرٌ مؤكد الحدوث، إذ تنفذ React أية تأثيرات لتصيير سابق قبل بدء تحديث جديد.
+
رغم أنَّ الخطاف <code>useEffect</code> مؤجَّل لبعد انتهاء المتصفح من الرسم، فإنَّ تنفيذه قبل أية عمليات تصيير أخرى أمرٌ مؤكد الحدوث، إذ تنفذ [[React]] أية تأثيرات لتصيير سابق قبل بدء تحديث جديد.
  
=== تنفيذ تأثير شرطيًّا ===
+
==== تنفيذ تأثير شرطيًّا ====
 
السلوك الافتراضي للتأثيرات هو تنفيذ التصيير بعد كل عملية تصيير مكتملة. بهذه الطريقة، يعاد إنشاء تأثيرٍ دومًا إن تغيرت إحدى مدخلاته.
 
السلوك الافتراضي للتأثيرات هو تنفيذ التصيير بعد كل عملية تصيير مكتملة. بهذه الطريقة، يعاد إنشاء تأثيرٍ دومًا إن تغيرت إحدى مدخلاته.
  
على أية حال، هذا السلوك قد يبدو مبالغًا فيه بشدة في بعض الحالات مثل مثال الاشتراك من القسم السابق. لا نحتاج إلى إنشاء اشتراك جديد في كل تحديث إلا إذا تغيرات الخاصية source.
+
على أية حال، هذا السلوك قد يبدو مبالغًا فيه بشدة في بعض الحالات كما في حالة مثال الاشتراك من القسم السابق. لا نحتاج إلى إنشاء اشتراك جديد في كل تحديث إلا إذا تغيرت الخاصية <code>source</code>.
  
لتنفيذ ذلك، مرِّر وسيطًا ثانيًّا إلى الخطاف useEffect يمثِّل مصفوفة القيم التي يعتمد عليها التأثير. يبدو الآن مثالنا بالمحدَّث بالشكل التالي:<syntaxhighlight lang="javascript">
+
لتنفيذ ذلك، مرِّر وسيطًا ثانيًّا إلى الخطاف <code>useEffect</code> يمثِّل مصفوفة القيم التي يعتمد عليها التأثير. يبدو الآن مثالنا المعدَّل بالشكل التالي:<syntaxhighlight lang="javascript">
 
useEffect(
 
useEffect(
 
   () => {
 
   () => {
سطر 98: سطر 98:
 
   [props.source],
 
   [props.source],
 
);
 
);
</syntaxhighlight>الآن، سيعاد إنشاء الاشتراك عند تغيِّر props.source.
+
</syntaxhighlight>الآن، سيعاد إنشاء الاشتراك عند تغيِّر <code>props.source</code>.
  
تمرير مصفوفة فارغة [] من المدخلات يخبر React أن تأثيراتك لا تعتمد على أية قيم من المكونات؛ لذلك، سيُنفَّذ ذلك التأثير عند الوصل (mount) ويُنظَّف عند الفصل (unmount) ولن تُنفَّذ عند التحديثات.
+
تمرير مصفوفة فارغة <code>[]</code> من المدخلات يخبر [[React]] أنَّ تأثيراتك لا تعتمد على أية قيم من المكونات؛ لذلك، سيُنفَّذ ذلك التأثير عند الوصل (mount) ويُنظَّف عند الفصل (unmount) ولن تُنفَّذ عند التحديثات.
  
ملاحظة: مصفوفة المدخلات لا تُمرَّر كوسائط إلى دالة التأثير. نظريًّا، إليك ما الذي تمثله: كل قيمة أشير إليها داخل دالة التأثير يجب أن تظهر أيضًا في مصفوفة المدخلات. في المستقبل، قد يصبح المصرف متقدم بما فيه الكفاية لإنشاء هذه المصفوفة تلقائيًا.
+
'''ملاحظة''': مصفوفة المدخلات لا تُمرَّر كوسائط إلى دالة التأثير. نظريًّا، إليك ما الذي تمثله: كل قيمة أشير إليها داخل دالة التأثير يجب أن تظهر أيضًا في مصفوفة المدخلات. في المستقبل، قد يصبح المصرِّف متقدمًا بما فيه الكفاية لإنشاء هذه المصفوفة تلقائيًا.
  
 
=== <code>useContext</code> ===
 
=== <code>useContext</code> ===
سطر 108: سطر 108:
 
const context = useContext(Context);
 
const context = useContext(Context);
  
</syntaxhighlight>يقبل هذا الخطاف كائن سياق (context object، أي القيمة المعادة من React.createContext) ويعيد قيمة السياق الحالي كما أُعطيَت من قبل أقرب موفر سياق (context provider) للسياق المعطى.
+
</syntaxhighlight>يقبل هذا الخطاف كائن سياق (context object، أي القيمة المعادة من <code>React.createContext</code>) ويعيد قيمة السياق الحالي كما أُعطيَت من قبل أقرب موفِّر سياق (context provider) للسياق المعطى.
  
عندما يتحدث السياق، سيُطلِق (trigger) هذا الخطاف عملية تصيير مع أحدث قيمة للسياق.
+
عندما يجري تحديث السياق، سيُطلِق (trigger) هذا الخطاف عملية تصيير مع أحدث قيمة للسياق.
 +
 
 +
== خطافات إضافية ==
 +
الخطافات التالية هي إمَّا شكل آخر للخطافات الأساسية أو يُلجَأ إليها في حالات محدَّدة فقط. لا تجهد نفسك بتعلمهم الآن إن لم تكن بحاجة لهم.
 +
 
 +
=== <code>useReducer</code> ===
 +
<syntaxhighlight lang="javascript">
 +
const [state, dispatch] = useReducer(reducer, initialArg, init);
 +
</syntaxhighlight>هذا الخطاف هو بديل للخطاف <code>[[React/hooks reference#useState|useState]]</code>. يقبل هذا الخطاف مخفِّضًا (reducer) من النوع ‎<code>(state, action) => newState</code> ويعيد الحالة الحالية مقرونةً مع التابع <code>dispatch</code>. (إن كانت المكتبة [https://redux.js.org/ Redux] مألوفةً لك، فأنت تعرف مسبقًا كيف يعمل ذلك.)
 +
 
 +
يفضَّل استعمال الخطاف <code>useReducer</code> عن الخطاف <code>useState</code> عندما يكون هنالك شيفرة حالة معقدة تتضمن قيم فرعية متعددة أو عندما تعتمد الحالة التالية على سابقتها. الخطاف <code>useReducer</code> يمكِّنك أيضًا من تحسين الأداء للمكونات التي تستدعي تحديثات عميقة لأنَّه يمكِّنك من تمرير التابع <code>dispatch</code> للداخل بدلًا من ردود النداء.
 +
 
 +
إليك مثال العداد الذي أعيد كتابته من القسم <code>[[React/hooks reference#useState|useState]]</code> ليستعمل مخفِّضًا:<syntaxhighlight lang="javascript">
 +
const initialState = {count: 0};
 +
 
 +
function reducer(state, action) {
 +
  switch (action.type) {
 +
    case 'increment':
 +
      return {count: state.count + 1};
 +
    case 'decrement':
 +
      return {count: state.count - 1};
 +
    default:
 +
      throw new Error();
 +
  }
 +
}
 +
 
 +
function Counter({initialState}) {
 +
  const [state, dispatch] = useReducer(reducer, initialState);
 +
  return (
 +
    <>
 +
      Count: {state.count}
 +
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
 +
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
 +
    </>
 +
  );
 +
}
 +
</syntaxhighlight>
 +
 
 +
==== تحديد الحالة الأولية ====
 +
هنالك طريقتان مختلفان لتهيئة حالة الخطاف <code>useReducer</code> يمكنك الاختيار بينهما بناءً على الحالة المستعملة آنذاك. الطريقة الأبسط هي تمرير الحالة الأولية كوسيطٍ ثانٍ:<syntaxhighlight lang="javascript">
 +
  const [state, dispatch] = useReducer(
 +
    reducer,
 +
    {count: initialCount}
 +
  );
 +
</syntaxhighlight>'''ملاحظة''': لا تستعمل [[React]] الوسيط <code>state = initialState</code> المتعارف عليه والشائع في المكتبة [https://redux.js.org/ Redux]. القيمة الأولية تعتمد أحيانًا على خاصيات وبذلك تُحدَّد من استدعاء الخطاف. إن كنت تشعر بشدة حيال ذلك، فيمكنك استدعاء <code>useReducer(reducer, undefined, reducer)‎</code> لمحاكاة سلوك [https://redux.js.org/ Redux] ولكن لا نشجع على ذلك.
 +
 
 +
==== التهيئة الكسولة ====
 +
يمكنك أيضًا إنشاء الحالة الأولية بتكاسل (lazily) عبر تمرير الدالة <code>init</code> كوسيط ثالث. ستُضبَط الحالة الأولية إلى <code>init(initialArg)‎</code>.
 +
 
 +
يمكِّنك ذلك من استخراج الشيفرة لحساب الحالة الأولية خارج المُخفِّض (reducer). هذا الأمر مفيدٌ وعملي لإعادة ضبط الحالة لاحقًا بالاستجابة لفعلٍ ما.<syntaxhighlight lang="javascript">
 +
function init(initialCount) {
 +
  return {count: initialCount};
 +
}
 +
 
 +
function reducer(state, action) {
 +
  switch (action.type) {
 +
    case 'increment':
 +
      return {count: state.count + 1};
 +
    case 'decrement':
 +
      return {count: state.count - 1};
 +
    case 'reset':
 +
      return init(action.payload);
 +
    default:
 +
      throw new Error();
 +
  }
 +
}
 +
 
 +
function Counter({initialCount}) {
 +
  const [state, dispatch] = useReducer(reducer, initialCount, init);
 +
  return (
 +
    <>
 +
      Count: {state.count}
 +
      <button
 +
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
 +
 
 +
        Reset
 +
      </button>
 +
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
 +
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
 +
    </>
 +
  );
 +
}
 +
</syntaxhighlight>
 +
 
 +
==== عدم تغير الحالة عن إجراء تحديث عليها ====
 +
إن أعيدت القيمة نفسها من خطافٍ مخفِّضٍ (Reducer Hook) والتي تمثِّل الحالة الحالية، فلن تتكبد React عناء تصيير الابن أو تنفيذ التأثيرات. (تستعمل [[React]] [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#Description الخوارزمية Object.is] لإجراء عملية الموازنة.)
 +
 
 +
=== <code>useCallback</code> ===
 +
<syntaxhighlight lang="javascript">
 +
const memoizedCallback = useCallback(
 +
  () => {
 +
    doSomething(a, b);
 +
  },
 +
  [a, b],
 +
);
 +
</syntaxhighlight>يُمرَّر إلى هذا الخطاف رد نداء سطري (inline callback) ومصفوفة من المدخلات، ويعيد نسخة مُستظهَرة ([[wikipedia:Memoization|memoized]] version، أي محفوظة دون إعادة تكرار العملية) من رد النداء المعطى والذي يتغير إن تغيرت قيمة إحدى مدخلاته فقط. هذا السلوك مفيدٌ للغاية عند تمرير ردود نداء لتحسين المكونات الأبناء الي تعتمد على المساواة المرجعية (reference equality) لمنع عمليات التصيير الغير ضرورية (مثل shouldComponentUpdate).
 +
 
 +
الاستدعاء <code>useCallback(fn, inputs)‎</code> مكافئ للاستدعاء <code>useMemo(() => fn, inputs)‎</code>.
 +
 
 +
'''ملاحظة''': مصفوفة المدخلات لا تُمرَّر كوسائط إلى رد النداء. نظريًّا، إليك ما الذي تمثله: كل قيمة أشير إليها داخل رد النداء يجب أن تظهر أيضًا في مصفوفة المدخلات. في المستقبل، قد يصبح المصرف متقدمًا بما فيه الكفاية لإنشاء هذه المصفوفة تلقائيًا.
 +
 
 +
=== <code>useMemo</code> ===
 +
<syntaxhighlight lang="javascript">
 +
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
 +
</syntaxhighlight>يعيد هذا الخطاف قيمةً مُستظهَرةً ([[wikipedia:Memoization|memoized]] value).
 +
 
 +
يُمرَّر إلى الخطاف <code>useMemo</code> دالة إنشاء (create function) ومصفوفة من المدخلات. سيحسب الخطاف القيمة المُستظهَرة عند تغيِّر إحدى المدخلات فقط. يساعد هذا التحسين في تجنب إعادة إجراء عمليات الحساب المستهلكة للأداء عند كل تصيير.
 +
 
 +
تذكر أنَّ الدالة المُمرَّرة إلى الخطاف <code>useMemo</code> تُنفَّذ أثناء عملية التصيير. لا تفعل أي شيء إضافي لا تفعله عادةً أثناء عملية التصيير. على سبيل المثال، التأثيرات الجانبية تنتمي إلى الخطاف <code>useEffect</code> وليس إلى الخطاف <code>useMemo</code>.
 +
 
 +
إن لم تُعطَ أية مصفوفة، ستُحسَب قيمةٌ جديدةٌ متى ما مُرِّرت نسخة دالة جديدة كأول وسيط. (أو في كل عملية تصيير مع دالة سطرية [inline function])
 +
 
 +
يمكنك الاعتماد على الخطاف <code>useMemo</code> لتحسين الأداء، وليس لضمان الدلالات (semantic guarantee). في المستقبل، قد تختار [[React]] بأن "تنسى" بعض القيم المُستظهَرة (المحفوظة) وتعيد حسابهم من جديد في عملية التصيير التالية وذلك لتحرير الذاكرة لمكونات غير ظاهرة على الشاشة مثلًا. اكتب أولًا شيفرتك لتعمل بشكل صحيح دون الخطاف <code>useMemo</code>، ومن ثمَّ أضفه لتحسين الأداء.
 +
 
 +
'''ملاحظة''': مصفوفة المدخلات لا تُمرَّر كوسائط إلى الدالة. نظريًّا، إليك ما الذي تمثله: كل قيمة أُشيِر إليها داخل الدالة يجب أن تظهر أيضًا في مصفوفة المدخلات. في المستقبل، قد يصبح المصرف متقدمًا بما فيه الكفاية لإنشاء هذه المصفوفة تلقائيًا.
 +
 
 +
=== <code>useRef</code> ===
 +
<syntaxhighlight lang="javascript">
 +
const refContainer = useRef(initialValue);
 +
 
 +
</syntaxhighlight>يعيد هذا الخطاف كائنًا مرجعيًّا قابلًا للتعديل (mutable ref object) تُهيَّأ الخاصية ‎<code>.current</code> فيه إلى قيمة الوسيط المُمرَّر (أي الوسيط <code>initialValue</code>). قد يبقى الكائن المعاد حتى كامل دورة حياة المكون.
 +
 
 +
حالة الاستعمال الشائعة لهذا الخطاف هي الحاجة إلى الوصول إلى ابنٍ بشكل إلزامي:<syntaxhighlight lang="javascript">
 +
function TextInputWithFocusButton() {
 +
  const inputEl = useRef(null);
 +
  const onButtonClick = () => {
 +
    // (mounted) إلى عنصر الإدخال النصي الموصول `current` يشير
 +
    inputEl.current.focus();
 +
  };
 +
  return (
 +
    <>
 +
      <input ref={inputEl} type="text" />
 +
      <button onClick={onButtonClick}>Focus the input</button>
 +
    </>
 +
  );
 +
}
 +
</syntaxhighlight>انتبه إلى أنَّ نفع الخطاف <code>useRef</code> يتجاوز الخاصية <code>ref</code>، إذ هو مفيدٌ جدًا في الإبقاء على أية قيمة قابلة للتعديل في متناول اليد. بشكل مشابه لكيفية استعمال حقول النسخ (instance fields) في الأصناف.
 +
 
 +
=== <code>useImperativeHandle</code> ===
 +
<syntaxhighlight lang="javascript">
 +
useImperativeHandle(ref, createHandle, [inputs])
 +
</syntaxhighlight>يخصِّص هذا الخطاف نسخة المتغير التي تُعرَض لمكون أب عند استعمال <code>ref</code>. كما هو الحال دومًا، الشيفرة الأمرية التي تستعمل المراجع (refs) يجب أن تُتجنَّب في أغلب الحالات.
 +
 
 +
الخطاف <code>useImperativeHandle</code> يجب أن يُستعمَل مع <code>forwardRef</code> بالشكل التالي:<syntaxhighlight lang="javascript">
 +
function FancyInput(props, ref) {
 +
  const inputRef = useRef();
 +
  useImperativeHandle(ref, () => ({
 +
    focus: () => {
 +
      inputRef.current.focus();
 +
    }
 +
  }));
 +
  return <input ref={inputRef} ... />;
 +
}
 +
FancyInput = forwardRef(FancyInput);
 +
</syntaxhighlight>في هذا المثال، سيكون المكون الأب الذي يصير ‎<code><FancyInput ref={fancyInputRef} /></code>‎ قادرًا على استدعاء <code>fancyInputRef.current.focus()‎</code>.
 +
 
 +
=== <code>useLayoutEffect</code> ===
 +
توقيع هذا الخطاف مماثل تمامًا للخطاف <code>useEffect</code> ولكن يُنفَّذ بشكل متزامن بعد إجراء كل التعديلات على DOM. استعمل هذا الخطاف لقراءة التخطيط (layout) من شجرة DOM وإعادة التصيير بشكل متزامن. ستُجرَى أية تحديثات مجدولة داخل الخطاف <code>useLayoutEffect</code> بشكل متزامن قبل أن يملك المتصفح فرصةً لإجراء عملية الرسم.
 +
 
 +
يفضل استعمال الخطاف <code>useEffect</code> الأساسي متى ما أمكنك ذلك لتجنب حجز التحديثات البصرية.
 +
 
 +
'''نصيحة''': إن كنت تستبدل شيفرة كتُبَت عبر مكون صنف أو هجرت مكون صنف وأردت استعمال الخطافات، يُنفَّذ الخطاف <code>useLayoutEffect</code> في نفس المرحلة التي ينفَّذ فيها التابعان <code>componentDidMount</code> و <code>componentDidUpdate</code>، لذا إن لم تكن متأكدًا أيَّ خطاف تأثير تريد استعماله، فهذا الخطاف يرجَّح أن يكون أقل خطورة.
 +
 
 +
=== <code>useDebugValue</code> ===
 +
<syntaxhighlight lang="javascript">
 +
useDebugValue(value)
 +
 
 +
</syntaxhighlight>يمكن استعمال هذا الخطاف لإظهار تسمية (label)  للخطافات المخصصة في أدوات تطوير [[React]] (أي React DevTools).
 +
 
 +
على سبيل المثال، افترض أننا عرفنا الخطاف <code>useFriendStatus</code> المخصص الذي شرحناه في صفحة "[[React/hooks custom|بناء خطاف خاص بك]]":<syntaxhighlight lang="javascript">
 +
function useFriendStatus(friendID) {
 +
  const [isOnline, setIsOnline] = useState(null);
 +
 
 +
  // ...
 +
 
 +
  // بجانب هذا الخطاف (DevTools) اظهار تسمية في أدوات التطوير
 +
  // "FriendStatus: Online" أي
 +
  useDebugValue(isOnline ? 'Online' : 'Offline');
 +
 
 +
  return isOnline;
 +
}
 +
</syntaxhighlight>'''نصيحة''': لا ننصح بإضافة قيم تنقيح الأخطاء (debug values) لكل خطاف مخصَّص. الشيء الأكثر نفعًا وقيمةً للخطافات المخصصة هو أن تكون جزءًا من المكتبات المشتركة (shared libraries).
 +
 
 +
==== الصيغة المؤجلة لقيم التنقيح ====
 +
في بعض الحالات، قد تستهلك عملية تنسيق قيمة لإظهارها الكثير من الأداء. أضف إلى ذلك أنَّها غير ضرورية إلا إذا كان يجري فحص خطاف محدَّد.
 +
 
 +
لذلك السبب، يقبل الخطاف <code>useDebugValue</code> دالة تنسيق يمكن تمريرها كوسيطٍ ثانٍ اختياريًّا. تُستدعَى هذا الدالة فقط إن كان الخطاف قيد الفحص (inspect)، ويمرَّر إليها قيمة التنقيح كوسيط ويجب أن تعيد قيمة منسقة قابلة للعرض.
 +
 
 +
على سبيل المثال، الخطاف المخصص الذي يعيد القيمة <code>[[JavaScript/Date|Date]]</code> يستطيع أن يتجنب استدعاء الدالة <code>toDateString</code> بشكل غير ضروري عبر تمرير المنسِّق التالي:<syntaxhighlight lang="javascript">
 +
useDebugValue(date, date => date.toDateString());
 +
</syntaxhighlight>
 +
 
 +
== انظر أيضًا ==
 +
*[[React/hooks intro|مدخل إلى الخطافات]]
 +
*[[React/hooks overview|لمحة خاطفة عن الخطافات]]
 +
*[[React/hooks state|استعمال خطاف الحالة]]
 +
*[[React/hooks effect|استعمال خطاف التأثير]]
 +
*[[React/hooks rules|قواعد استعمال الخطافات]]
 +
*[[React/hooks custom|بناء خطاف خاص بك]]
 +
*[[React/hooks faq|الأسئلة الشائعة حول الخطافات]]
 +
==مصادر==
 +
*[https://reactjs.org/docs/hooks-reference.html صفحة مرجع إلى الواجهة البرمجية للخطافات في توثيق React الرسمي.]

مراجعة 09:28، 17 فبراير 2019

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

تشرح هذه الصفحة الواجهات البرمجية للخطافات المضمَّنة في React.

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

الخطافات الأساسية

useState

const [state, setState] = useState(initialState);

يعيد هذا الخطاف قيمةً ذات حالة، ودالةً لتحديث هذه القيمة.

أثناء عملية التصيير الأولية، الحالة المعادة (state) هي نفسها القيمة المُمرَّرة كأول وسيط (initialState).

تُستعمَل الدالة setState لتحديث الحالة، إذ تقبل قيمةً جديدةً للحالة وتدرج في الطابور عملية إعادة تصيير لمكون.

setState(newState);

أثناء عمليات إعادة التصيير اللاحقة، القيمة الأولى التي يعيدها الخطاف useState ستبقى دومًا أحدث حالة بعد تطبيق التحديثات.

تحديثات عبر تمرير دالة

إن حُسبَت الحالة الجديدة باستعمال الحالة السابقة، فيمكنك تمرير دالة إلى setState.ستستقبل الدالة القيمة السابقة، وتعيد القيمة المحدَّثة. إليك مثالٌ عن مكون عداد يستعمل كلا الشكلين للخطاف setState:

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    </>
  );
}

يستعمل الزر "+" والزر "-" الشكل الدالِّي (functional form) لأنَّ القيمة المحدَّثة تعتمد على القيمة السابقة. ولكن الزر "Reset" يستعمل الشكل الاعتيادي لأنَّه يضبط العداد إلى القيمة 0 دومًا. ملاحظة: خلافًا للتابع setState في مكونات الأصناف، الخطاف useState لا يدمج كائنات التحديث (update objects). يمكنك تكرار هذا السلوك عبر دمج شكل الدالة المحدِّثة مع معامل النشر للكائن:

setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});

هنالك خيار آخر وهو استعمال الخطاف useReducer الذي يعدُّ مناسبًا لإدارة كائنات حالة تحوي عدة قيم فرعية.

الحالة الأولية الكسولة

الوسيط initialState هو الحالة المستعملة أثناء عملية التصيير الأولى (initial render). في عمليات التصيير اللاحقة، سيُهمَل هذا الوسيط. إن كانت الحالة الأولية هي ناتج عملية حساب معقدة تؤثر على الأداء، فيمكنك أن تمرِّر دالةً تُنفَّذ مرةً واحدةً في أول عملية تصيير عوضًا عن ذلك:

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

عدم تغير الحالة عن إجراء تحديث عليها

إن حدَّث خطاف حالة وكانت القيمة المحدَّثة نفسَ قيمة الحالة الحالية، فلن تتكبد React عناء تصيير الابن أو تنفيذ التأثيرات. (تستعمل React الخوارزمية Object.is لإجراء عملية الموازنة.)

useEffect

useEffect(didUpdate);

يقبل هذا الخطاف دالةً تحوي أمرًا يكون غالبًا شيفرةً ذات تأثير.

التعديلات، والاشتراكات، والمؤقتات، والسجلات، والتسجيل (logging)، والتأثيرات الجانبية الأخرى غير مسموحٍ بها داخل الجسم الرئيسي لمكون دالة (يشار إليه على أنَّه مرحلة تصيير React [أي render phase]). سيؤدي فعل ذلك إلى حصول أخطاءٍ مربكة مع تناقضات في واجهة المستخدم.

عوضًا عن ذلك، استعمل الخطاف useEffect. الدالة المُمرَّر إليه ستُنفَّذ بعد الانتهاء من التصيير على الشاشة. فكر في التأثيرات وكأنَّها مخرج هروب (escape hatch) من عالم React الوظيفي البحت إلى العالم الأمري.

افتراضيًّا، تُنفَّذ التأثيرات بعد كل كل عملية تصيير مكتملة، ولكن يمكنك اختيار تنفيذها فقط عند تغير قيم محدَّدة.

تنظيف تأثير

تنشئ التأثيرات غالبًا موارد تحتاج للتنظيف قبل أن يغادر المكون الشاشة مثل معرِّف اشتراك أو مُؤقِّت. لفعل ذلك، قد تعيد الدالة المُمرَّرة إلى الخطاف useEffect دالةً تجري عملية التنظيف. على سبيل المثال، يمكن إنشاء اشتراك بالشكل التالي:

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // تنظيف الاشتراك
    subscription.unsubscribe();
  };
});

تُنفَّذ دالة التنظيف قبل حذف المكون من واجهة المستخدم لمنع حدوث تسريب في الذاكرة. أضف إلى ذلك أنَّه إن صُيَّر مكوِّنٌ عدَّة مرات (كما تفعل عادةً)، فسيُنظَّف التأثير السابق قبل تنفيذ التأثير اللاحق. في مثالنا، هذا يعني أنَّه يُنشَأ اشتراك جديد في كل تحديث. لتجنب تنفيذ التأثير عند كل عملية تحديث، ارجع إلى القسم التالي.

توقيت التأثيرات

خلافًا للتابعين componentDidMount و componentDidUpdate، تُنفَّذ الدالة المُمرَّرة إلى الخطاف useEffect بعد التخطيط والرسم أثناء حدث مؤجَّل (deferred event). هذا يجعلها مناسبة للاستعمال مع العديد من التأثيرات الجانبية الشائعة مثل ضبط الاشتراكات ومعالجات الحدث لأنَّ أغلب أنواع العمل لا يجب أن يحجز المتصفح عن تحديث الشاشة.

على أية حال، لا يمكن تأجيل جميع التأثيرات. على سبيل المثال، يجب أن تُنفَّذ التعديلات التي تجرَى على DOM والظاهرة للمستخدم بشكل متزامن قبل تنفيذ عملية الرسم التالية، لذا لا يلاحظ المستخدم أية تناقضات بصرية. (الفارق من ناحية النظرية مشابهٌ للفارق بين مستمعي حدث نشطين مقابل مستمعي حدث خاملين.) توفر React من أجل هذه الأنواع من التأثيرات خطافًا إضافيًّا يدعى useLayoutEffect. يملك هذا الخطاف نفس التوقيع الذي يملكه الخطاف useEffect. الاختلاف الوحيد بينهما هو وقت التنفيذ.

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

تنفيذ تأثير شرطيًّا

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

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

لتنفيذ ذلك، مرِّر وسيطًا ثانيًّا إلى الخطاف useEffect يمثِّل مصفوفة القيم التي يعتمد عليها التأثير. يبدو الآن مثالنا المعدَّل بالشكل التالي:

useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],
);

الآن، سيعاد إنشاء الاشتراك عند تغيِّر props.source.

تمرير مصفوفة فارغة [] من المدخلات يخبر React أنَّ تأثيراتك لا تعتمد على أية قيم من المكونات؛ لذلك، سيُنفَّذ ذلك التأثير عند الوصل (mount) ويُنظَّف عند الفصل (unmount) ولن تُنفَّذ عند التحديثات.

ملاحظة: مصفوفة المدخلات لا تُمرَّر كوسائط إلى دالة التأثير. نظريًّا، إليك ما الذي تمثله: كل قيمة أشير إليها داخل دالة التأثير يجب أن تظهر أيضًا في مصفوفة المدخلات. في المستقبل، قد يصبح المصرِّف متقدمًا بما فيه الكفاية لإنشاء هذه المصفوفة تلقائيًا.

useContext

const context = useContext(Context);

يقبل هذا الخطاف كائن سياق (context object، أي القيمة المعادة من React.createContext) ويعيد قيمة السياق الحالي كما أُعطيَت من قبل أقرب موفِّر سياق (context provider) للسياق المعطى.

عندما يجري تحديث السياق، سيُطلِق (trigger) هذا الخطاف عملية تصيير مع أحدث قيمة للسياق.

خطافات إضافية

الخطافات التالية هي إمَّا شكل آخر للخطافات الأساسية أو يُلجَأ إليها في حالات محدَّدة فقط. لا تجهد نفسك بتعلمهم الآن إن لم تكن بحاجة لهم.

useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);

هذا الخطاف هو بديل للخطاف useState. يقبل هذا الخطاف مخفِّضًا (reducer) من النوع ‎(state, action) => newState ويعيد الحالة الحالية مقرونةً مع التابع dispatch. (إن كانت المكتبة Redux مألوفةً لك، فأنت تعرف مسبقًا كيف يعمل ذلك.)

يفضَّل استعمال الخطاف useReducer عن الخطاف useState عندما يكون هنالك شيفرة حالة معقدة تتضمن قيم فرعية متعددة أو عندما تعتمد الحالة التالية على سابقتها. الخطاف useReducer يمكِّنك أيضًا من تحسين الأداء للمكونات التي تستدعي تحديثات عميقة لأنَّه يمكِّنك من تمرير التابع dispatch للداخل بدلًا من ردود النداء.

إليك مثال العداد الذي أعيد كتابته من القسم useState ليستعمل مخفِّضًا:

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter({initialState}) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}

تحديد الحالة الأولية

هنالك طريقتان مختلفان لتهيئة حالة الخطاف useReducer يمكنك الاختيار بينهما بناءً على الحالة المستعملة آنذاك. الطريقة الأبسط هي تمرير الحالة الأولية كوسيطٍ ثانٍ:

  const [state, dispatch] = useReducer(
    reducer,
    {count: initialCount}
  );

ملاحظة: لا تستعمل React الوسيط state = initialState المتعارف عليه والشائع في المكتبة Redux. القيمة الأولية تعتمد أحيانًا على خاصيات وبذلك تُحدَّد من استدعاء الخطاف. إن كنت تشعر بشدة حيال ذلك، فيمكنك استدعاء useReducer(reducer, undefined, reducer)‎ لمحاكاة سلوك Redux ولكن لا نشجع على ذلك.

التهيئة الكسولة

يمكنك أيضًا إنشاء الحالة الأولية بتكاسل (lazily) عبر تمرير الدالة init كوسيط ثالث. ستُضبَط الحالة الأولية إلى init(initialArg)‎.

يمكِّنك ذلك من استخراج الشيفرة لحساب الحالة الأولية خارج المُخفِّض (reducer). هذا الأمر مفيدٌ وعملي لإعادة ضبط الحالة لاحقًا بالاستجابة لفعلٍ ما.

function init(initialCount) {
  return {count: initialCount};
}

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>

        Reset
      </button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}

عدم تغير الحالة عن إجراء تحديث عليها

إن أعيدت القيمة نفسها من خطافٍ مخفِّضٍ (Reducer Hook) والتي تمثِّل الحالة الحالية، فلن تتكبد React عناء تصيير الابن أو تنفيذ التأثيرات. (تستعمل React الخوارزمية Object.is لإجراء عملية الموازنة.)

useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

يُمرَّر إلى هذا الخطاف رد نداء سطري (inline callback) ومصفوفة من المدخلات، ويعيد نسخة مُستظهَرة (memoized version، أي محفوظة دون إعادة تكرار العملية) من رد النداء المعطى والذي يتغير إن تغيرت قيمة إحدى مدخلاته فقط. هذا السلوك مفيدٌ للغاية عند تمرير ردود نداء لتحسين المكونات الأبناء الي تعتمد على المساواة المرجعية (reference equality) لمنع عمليات التصيير الغير ضرورية (مثل shouldComponentUpdate).

الاستدعاء useCallback(fn, inputs)‎ مكافئ للاستدعاء useMemo(() => fn, inputs)‎.

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

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

يعيد هذا الخطاف قيمةً مُستظهَرةً (memoized value).

يُمرَّر إلى الخطاف useMemo دالة إنشاء (create function) ومصفوفة من المدخلات. سيحسب الخطاف القيمة المُستظهَرة عند تغيِّر إحدى المدخلات فقط. يساعد هذا التحسين في تجنب إعادة إجراء عمليات الحساب المستهلكة للأداء عند كل تصيير.

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

إن لم تُعطَ أية مصفوفة، ستُحسَب قيمةٌ جديدةٌ متى ما مُرِّرت نسخة دالة جديدة كأول وسيط. (أو في كل عملية تصيير مع دالة سطرية [inline function])

يمكنك الاعتماد على الخطاف useMemo لتحسين الأداء، وليس لضمان الدلالات (semantic guarantee). في المستقبل، قد تختار React بأن "تنسى" بعض القيم المُستظهَرة (المحفوظة) وتعيد حسابهم من جديد في عملية التصيير التالية وذلك لتحرير الذاكرة لمكونات غير ظاهرة على الشاشة مثلًا. اكتب أولًا شيفرتك لتعمل بشكل صحيح دون الخطاف useMemo، ومن ثمَّ أضفه لتحسين الأداء.

ملاحظة: مصفوفة المدخلات لا تُمرَّر كوسائط إلى الدالة. نظريًّا، إليك ما الذي تمثله: كل قيمة أُشيِر إليها داخل الدالة يجب أن تظهر أيضًا في مصفوفة المدخلات. في المستقبل، قد يصبح المصرف متقدمًا بما فيه الكفاية لإنشاء هذه المصفوفة تلقائيًا.

useRef

const refContainer = useRef(initialValue);

يعيد هذا الخطاف كائنًا مرجعيًّا قابلًا للتعديل (mutable ref object) تُهيَّأ الخاصية ‎.current فيه إلى قيمة الوسيط المُمرَّر (أي الوسيط initialValue). قد يبقى الكائن المعاد حتى كامل دورة حياة المكون. حالة الاستعمال الشائعة لهذا الخطاف هي الحاجة إلى الوصول إلى ابنٍ بشكل إلزامي:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // (mounted) إلى عنصر الإدخال النصي الموصول `current` يشير
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

انتبه إلى أنَّ نفع الخطاف useRef يتجاوز الخاصية ref، إذ هو مفيدٌ جدًا في الإبقاء على أية قيمة قابلة للتعديل في متناول اليد. بشكل مشابه لكيفية استعمال حقول النسخ (instance fields) في الأصناف.

useImperativeHandle

useImperativeHandle(ref, createHandle, [inputs])

يخصِّص هذا الخطاف نسخة المتغير التي تُعرَض لمكون أب عند استعمال ref. كما هو الحال دومًا، الشيفرة الأمرية التي تستعمل المراجع (refs) يجب أن تُتجنَّب في أغلب الحالات. الخطاف useImperativeHandle يجب أن يُستعمَل مع forwardRef بالشكل التالي:

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

في هذا المثال، سيكون المكون الأب الذي يصير ‎<FancyInput ref={fancyInputRef} />‎ قادرًا على استدعاء fancyInputRef.current.focus()‎.

useLayoutEffect

توقيع هذا الخطاف مماثل تمامًا للخطاف useEffect ولكن يُنفَّذ بشكل متزامن بعد إجراء كل التعديلات على DOM. استعمل هذا الخطاف لقراءة التخطيط (layout) من شجرة DOM وإعادة التصيير بشكل متزامن. ستُجرَى أية تحديثات مجدولة داخل الخطاف useLayoutEffect بشكل متزامن قبل أن يملك المتصفح فرصةً لإجراء عملية الرسم.

يفضل استعمال الخطاف useEffect الأساسي متى ما أمكنك ذلك لتجنب حجز التحديثات البصرية.

نصيحة: إن كنت تستبدل شيفرة كتُبَت عبر مكون صنف أو هجرت مكون صنف وأردت استعمال الخطافات، يُنفَّذ الخطاف useLayoutEffect في نفس المرحلة التي ينفَّذ فيها التابعان componentDidMount و componentDidUpdate، لذا إن لم تكن متأكدًا أيَّ خطاف تأثير تريد استعماله، فهذا الخطاف يرجَّح أن يكون أقل خطورة.

useDebugValue

useDebugValue(value)

يمكن استعمال هذا الخطاف لإظهار تسمية (label) للخطافات المخصصة في أدوات تطوير React (أي React DevTools). على سبيل المثال، افترض أننا عرفنا الخطاف useFriendStatus المخصص الذي شرحناه في صفحة "بناء خطاف خاص بك":

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  // بجانب هذا الخطاف (DevTools) اظهار تسمية في أدوات التطوير
  // "FriendStatus: Online" أي
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

نصيحة: لا ننصح بإضافة قيم تنقيح الأخطاء (debug values) لكل خطاف مخصَّص. الشيء الأكثر نفعًا وقيمةً للخطافات المخصصة هو أن تكون جزءًا من المكتبات المشتركة (shared libraries).

الصيغة المؤجلة لقيم التنقيح

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

لذلك السبب، يقبل الخطاف useDebugValue دالة تنسيق يمكن تمريرها كوسيطٍ ثانٍ اختياريًّا. تُستدعَى هذا الدالة فقط إن كان الخطاف قيد الفحص (inspect)، ويمرَّر إليها قيمة التنقيح كوسيط ويجب أن تعيد قيمة منسقة قابلة للعرض.

على سبيل المثال، الخطاف المخصص الذي يعيد القيمة Date يستطيع أن يتجنب استدعاء الدالة toDateString بشكل غير ضروري عبر تمرير المنسِّق التالي:

useDebugValue(date, date => date.toDateString());

انظر أيضًا

مصادر