paint-brush
إتقان React من خلال تصميم واجهات برمجة تطبيقات فعالة باستخدام هوك useImperativeHandleبواسطة@socialdiscoverygroup
تاريخ جديد

إتقان React من خلال تصميم واجهات برمجة تطبيقات فعالة باستخدام هوك useImperativeHandle

بواسطة Social Discovery Group11m2024/12/23
Read on Terminal Reader

طويل جدا؛ ليقرأ

يتيح الخطاف useImperativeHandle في React للمطورين تخصيص الأساليب والخصائص التي يعرضها أحد المكونات، مما يعزز المرونة وسهولة الصيانة. ويعمل مع forwardRef لتوفير واجهة برمجية للمكونات الفرعية، مما يتيح التحكم المباشر في سلوكها. وتتضمن أفضل الممارسات عزل منطق المكون الفرعي، وتبسيط التكامل مع مكتبات الطرف الثالث، وتجنب الأخطاء الشائعة مثل مصفوفات التبعية غير الصحيحة. ومن خلال استخدام هذا الخطاف بشكل فعال، يمكن للمطورين إنشاء مكونات أكثر كفاءة وتحسين الأداء العام للتطبيق.
featured image - إتقان React من خلال تصميم واجهات برمجة تطبيقات فعالة باستخدام هوك useImperativeHandle
Social Discovery Group HackerNoon profile picture
0-item
1-item

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


في هذه القطعة، تتعمق رؤى فريق Social Discovery Group في أفضل الممارسات لاستخدام useImperativeHandle بشكل فعال لتحسين مكونات React.


يوفر React العديد من الخطافات (تصف الوثائق الرسمية 17 خطافًا حتى وقت كتابة هذه السطور) لإدارة الحالة والتأثيرات والتفاعلات بين المكونات.


من بينها، useImperativeHandle هي أداة مفيدة لإنشاء واجهة برمجية (API) للمكونات الفرعية، والتي تمت إضافتها إلى React من الإصدار 16.8.0 فصاعدًا.


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


 useImperativeHandle(ref, createHandle, [deps]);
  • ref — المرجع الذي تم تمريره إلى المكون.
  • createHandle — دالة تقوم بإرجاع كائن سيصبح قابلاً للوصول إليه عبر المرجع.
  • deps — مجموعة التبعية.


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

مثال أساسي


لنتخيل أننا بحاجة إلى معالجة DOM لمكون فرعي. فيما يلي مثال لكيفية القيام بذلك باستخدام مرجع.

 import React, { forwardRef, useRef } from 'react'; const CustomInput = forwardRef((props, ref) => { // Use forwardRef to pass the ref to the input element return <input ref={ref} {...props} />; }); export default function App() { const inputRef = useRef(); const handleFocus = () => { inputRef.current.focus(); // Directly controlling the input }; const handleClear = () => { inputRef.current.value = ''; // Directly controlling the input value }; return ( <div> <CustomInput ref={inputRef} /> <button onClick={handleFocus}>Focus</button> <button onClick={handleClear}>Clear</button> </div> ); }

وإليك كيفية تحقيق ذلك باستخدام useImperativeHandle.

 import React, { useImperativeHandle, forwardRef, useRef } from 'react'; const CustomInput = forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); }, clear: () => { inputRef.current.value = ''; }, })); return <input ref={inputRef} {...props} />; }); export default function App() { const inputRef = useRef(); return ( <div> <CustomInput ref={inputRef} /> <button onClick={inputRef.current.focus}>Focus</button> <button onClick={inputRef.current.clear}>Clear</button> </div> ); }


كما هو موضح في الأمثلة أعلاه، عند استخدام useImperativeHandle، يوفر مكون الطفل للمكون الأساسي مجموعة من الأساليب التي نحددها بأنفسنا.


مزايا استخدام ImperativeHandle مقارنة باستخدام ref فقط


  1. عزل منطق المكون الفرعي: يسمح بتوفير المكونات الأصلية فقط بالطرق المطلوبة.
  2. يُبسِّط التكامل: يجعل من الأسهل دمج مكونات React مع المكتبات التي تتطلب الوصول المباشر إلى DOM، مثل Lottie أو Three.js.

السيناريوهات المتقدمة

يسمح استخدام useImperativeHandle في السيناريوهات المتقدمة، مثل الأمثلة التي تحتوي على رسوم متحركة، بعزل السلوك المعقد داخل أحد المكونات. وهذا يجعل المكون الرئيسي أبسط وأكثر قابلية للقراءة، وخاصة عند العمل مع مكتبات الرسوم المتحركة أو الصوت.



 import React, { useRef, useState, useImperativeHandle, forwardRef, memo } from "react"; import { Player } from '@lottiefiles/react-lottie-player' import animation from "./animation.json"; const AnimationWithSound = memo( forwardRef((props, ref) => { const [isAnimating, setIsAnimating] = useState(false); const animationRef = useRef(null); const targetDivRef = useRef(null); useImperativeHandle( ref, () => ({ startAnimation: () => { setIsAnimating(true); animationRef.current?.play() updateStyles("start"); }, stopAnimation: () => { animationRef.current?.stop() updateStyles("stop"); }, }), [] ); const updateStyles = (action) => { if (typeof window === 'undefined' || !targetDivRef.current) return; if (action === "start") { if (targetDivRef.current.classList.contains(styles.stop)) { targetDivRef.current.classList.remove(styles.stop); } targetDivRef.current.classList.add(styles.start); } else if (action === "stop") { if (targetDivRef.current.classList.contains(styles.start)) { targetDivRef.current.classList.remove(styles.start); } targetDivRef.current.classList.add(styles.stop); } }; return ( <div> <Player src={animation} loop={isAnimating} autoplay={false} style={{width: 200, height: 200}} ref={animationRef} /> <div ref={targetDivRef} className="target-div"> This div changes styles </div> </div> ); }) ); export default function App() { const animationRef = useRef(); const handleStart = () => { animationRef.current.startAnimation(); }; const handleStop = () => { animationRef.current.stopAnimation(); }; return ( <div> <h1>Lottie Animation with Sound</h1> <AnimationWithSound ref={animationRef} /> <button onClick={handleStart}>Start Animation</button> <button onClick={handleStop}>Stop Animation</button> </div> ); }


في هذا المثال، يقوم المكون الفرعي بإرجاع الطرق startAnimation وstopAnimation، والتي تغلف المنطق المعقد داخل نفسها.


الأخطاء الشائعة والمزالق

1. مصفوفة التبعية المملوءة بشكل غير صحيح

لا يمكن ملاحظة الخطأ دائمًا على الفور. على سبيل المثال، قد يقوم المكون الرئيسي بتغيير الخصائص بشكل متكرر، وقد تواجه موقفًا حيث يستمر استخدام طريقة قديمة (ببيانات قديمة).


مثال على الخطأ:

https://use-Imperative-handle.levkovich.dev/deps-is-not-correct/wrong

 const [count, setCount] = useState(0); const increment = useCallback(() => { console.log("Current count in increment:", count); // Shows old value setCount(count + 1); // Are using the old value of count }, [count]); useImperativeHandle( ref, () => ({ increment, // Link to the old function is used }), [] // Array of dependencies do not include increment function );


النهج الصحيح:

https://use-Imperative-handle.levkovich.dev/deps-is-not-correct/correct

 const [count, setCount] = useState(0); useImperativeHandle( ref, () => ({ increment, }), [increment] // Array of dependencies include increment function );


2. مجموعة التبعيات المفقودة


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


مثال على الخطأ:

 useImperativeHandle(ref, () => { // There is might be a difficult task console.log("useImperativeHandle calculated again"); return { focus: () => {} } }); // Array of dependencies is missing


النهج الصحيح:

https://use-Imperative-handle.levkovich.dev/deps-empty/correct

 useImperativeHandle(ref, () => { // There is might be a difficult task console.log("useImperativeHandle calculated again"); return { focus: () => {} } }, []); // Array of dependencies is correct


  1. تعديل المرجع داخل useImperativeHandle

يؤدي التعديل المباشر لملف ref.current إلى تعطيل سلوك React. إذا حاول React تحديث الملف ref، فقد يؤدي ذلك إلى تعارضات أو أخطاء غير متوقعة.


مثال على الخطأ:

https://use-Imperative-handle.levkovich.dev/ref-modification/wrong


 useImperativeHandle(ref, () => { // ref is mutated directly ref.current = { customMethod: () => console.log("Error") }; });

النهج الصحيح:

https://use-Imperative-handle.levkovich.dev/ref-modification/correct


 useImperativeHandle(ref, () => ({ customMethod: () => console.log("Correct"), }));


  1. استخدام الأساليب قبل تهيئتها


قد يؤدي استدعاء الأساليب المقدمة عبر useImperativeHandle من useEffect أو معالجات الأحداث، على افتراض أن المرجع متاح بالفعل، إلى حدوث أخطاء - تحقق دائمًا من current قبل استدعاء أساليبه.


مثال على الخطأ:


https://use-Imperative-handle.levkovich.dev/before-init/wrong

 const increment = useCallback(() => { childRef.current.increment(); }, [])


النهج الصحيح:

https://use-Imperative-handle.levkovich.dev/before-init/correct


 const increment = useCallback(() => { if (childRef.current?.increment) { childRef.current.increment() } }, [])


  1. مشاكل المزامنة بين الرسوم المتحركة والحالة

إذا أرجعت useImperativeHandle طرقًا تغير الحالة بشكل متزامن (على سبيل المثال، بدء الرسوم المتحركة وتعديل الأنماط في نفس الوقت)، فقد يتسبب ذلك في "فجوة" بين الحالة المرئية والمنطق الداخلي. تأكد من الاتساق بين الحالة والسلوك المرئي، على سبيل المثال، باستخدام التأثيرات (useEffect).


مثال على الخطأ:

https://use-Imperative-handle.levkovich.dev/state-animation-sync/wrong


 useImperativeHandle(ref, () => ({ startAnimation: () => { setState("running"); // Animation starts before the state changes lottieRef.current.play(); }, stopAnimation: () => { setState("stopped"); // Animation stops before the state changes lottieRef.current.stop(); }, }));


النهج الصحيح:


https://use-Imperative-handle.levkovich.dev/state-animation-sync/correct

 useEffect(() => { if (state === "running" && lottieRef.current) { lottieRef.current.play(); } else if (state === "stopped" && lottieRef.current) { lottieRef.current.stop(); } }, [state]); // Triggered when the state changes useImperativeHandle( ref, () => ({ startAnimation: () => { setState("running"); }, stopAnimation: () => { setState("stopped"); }, }), [] );

خاتمة

يُعد استخدام useImperativeHandle مبررًا في المواقف التالية:


  • التحكم في سلوك مكون الطفل: على سبيل المثال، لتوفير طريقة التركيز أو إعادة التعيين لمكون إدخال معقد.

  • إخفاء تفاصيل التنفيذ: يتلقى المكون الرئيسي فقط الأساليب التي يحتاجها، وليس كائن المرجع بأكمله.


قبل استخدام useImperativeHandle، اسأل نفسك الأسئلة التالية:

  • هل يمكن حل المهمة بشكل إعلاني باستخدام الحالة أو الدعائم أو السياق؟ إذا كانت الإجابة بنعم، فهذا هو النهج المفضل.
  • إذا لم يكن الأمر كذلك، ويحتاج المكون إلى توفير واجهة خارجية، فاستخدم useImperativeHandle.


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


بقلم سيرجي ليفكوفيتش، مهندس برمجيات أول في مجموعة Social Discovery Group