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

على أي حال ،
لا أوصي بإعادة كتابة المكونات الموجودة باستخدام تقنيات جديدة إذا لم تكن هناك أسباب وجيهة لذلك ، وإذا كنت لا تمانع في أن تكون من بين أولئك الذين بدأوا في استخدام هذه التقنيات قبل أي شخص آخر. لا تزال الخطافات تقنية جديدة (كما كانت مكتبة React في عام 2014) ، ولم يتم تضمين بعض "أفضل الممارسات" لتطبيقها في أدلة React.
إلى أين أتينا أخيرًا؟ هل هناك أي اختلافات جوهرية بين المكونات الوظيفية في React والمكونات القائمة على الطبقات؟ بالطبع ، هناك مثل هذه الاختلافات. هذه اختلافات في النموذج العقلي لاستخدام هذه المكونات. في هذه المقالة سأنظر في الفرق الأكثر خطورة. لقد كان موجودًا منذ ظهور المكونات الوظيفية في عام 2015 ، ولكن غالبًا ما يتم تجاهله. يتكون في حقيقة أن المكونات الوظيفية تلتقط القيم المقدمة. دعنا نتحدث عن ما يعنيه هذا حقا.
تجدر الإشارة إلى أن هذه المادة لا تشكل محاولة لتقييم مكونات الأنواع المختلفة. أصف الفرق بين نموذجي البرمجة في React. إذا كنت ترغب في معرفة المزيد حول استخدام المكونات الوظيفية في ضوء الابتكارات ، فراجع قائمة الأسئلة والأجوبة على الروابط.
ما هي ميزات رمز المكونات بناءً على الوظائف وعلى الفئات؟
النظر في هذا المكون:
function ProfilePage(props) { const showMessage = () => { alert('Followed ' + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( <button onClick={handleClick}>Follow</button> ); }
يعرض زرًا ، يعمل بالضغط على وظيفة
setTimeout
محاكاة طلب شبكة ، ثم يعرض مربع رسالة لتأكيد العملية. على سبيل المثال ، إذا تم تخزين "
props.user
'Dan'
في
props.user
، فسيتم عرض
'Followed Dan'
في نافذة الرسائل ، بعد ثلاث ثوانٍ.
لاحظ أنه لا يهم ما إذا كان يتم استخدام وظائف السهم أو إعلانات الوظائف هنا.
function handleClick()
النموذج
function handleClick()
بنفس الطريقة تمامًا.
كيفية إعادة كتابة هذا المكون كصف؟ إذا قمت للتو بإعادة الكود الذي تم فحصه للتو ، وتحويله إلى رمز مكون بناءً على فصل دراسي ، فستحصل على ما يلي:
class ProfilePage extends React.Component { showMessage = () => { alert('Followed ' + this.props.user); }; handleClick = () => { setTimeout(this.showMessage, 3000); }; render() { return <button onClick={this.handleClick}>Follow</button>; } }
من المقبول عمومًا أن اثنين من أجزاء التعليمات البرمجية هذه متساويان. وغالبًا ما يكون المطورون مجانيون تمامًا ، أثناء إعادة تشفير الكود ، يحولون بعضهم إلى الآخر ، دون التفكير في العواقب المحتملة.
هذه القطع من الكود تبدو متكافئةومع ذلك ، هناك اختلاف بسيط بين مقتطفات الشفرة هذه. نلقي نظرة فاحصة عليها. انظر الفرق؟ على سبيل المثال ، لم أرها على الفور.
علاوة على ذلك ، سننظر في هذا الاختلاف ، وبالتالي ، بالنسبة لأولئك الذين يرغبون في فهم جوهر ما يحدث بأنفسهم ، فهو مثال عملي على هذا الرمز.
قبل المتابعة ، أود التأكيد على أن الاختلاف في السؤال لا علاقة له بخطافات React. في الأمثلة السابقة ، بالمناسبة ، لا تستخدم حتى السنانير. إنه يتعلق بالفرق بين الوظائف والطبقات في React. وإذا كنت تخطط لاستخدام العديد من المكونات الوظيفية في تطبيقات React الخاصة بك ، فقد ترغب في فهم هذا الاختلاف.
في الواقع ، سنقوم بتوضيح الفرق بين الوظائف والفئات من خلال مثال عن خطأ غالبًا ما يتم مواجهته في تطبيقات React.
الخطأ الشائع في تطبيقات React.
افتح
صفحة المثال التي تعرض قائمة تسمح لك بتحديد ملفات تعريف المستخدمين ، واثنين من أزرار المتابعة التي يتم عرضها بواسطة
ProfilePageClass
و
ProfilePageClass
، الوظيفية ، وعلى أساس الفئة ، يظهر رمزها أعلاه.
حاول ، لكل من هذه الأزرار ، تنفيذ تسلسل الإجراءات التالي:
- انقر فوق الزر.
- تغيير ملف التعريف المحدد قبل 3 ثوان تنقضي بعد النقر على الزر.
- اقرأ النص المعروض في مربع الرسالة.
بعد القيام بذلك ، ستلاحظ الميزات التالية:
- عند النقر فوق الزر الذي شكله المكون الوظيفي مع ملف تعريف
Dan
المحدد ثم التبديل إلى ملف تعريف Sophie
، سيتم عرض 'Followed Dan'
في مربع الرسالة. - إذا كنت تفعل الشيء نفسه باستخدام زر مكون من مكون يستند إلى فصل
'Followed Sophie'
فسيتم عرض 'Followed Sophie'
.
ميزات المكون المستندة إلى الفئةفي هذا المثال ، يكون سلوك المكون الوظيفي صحيحًا. إذا اشتركت في ملف تعريف شخص ما ، ثم انتقلت إلى ملف تعريف آخر ، فلا ينبغي أن يشك مكوّن بلدي في ملفي الشخصي الذي اشتركت فيه. من الواضح أن تنفيذ الآلية المعنية بناءً على استخدام الفصول يحتوي على خطأ (بالمناسبة ، يجب أن تصبح بالتأكيد مشتركًا في
صوفيا ).
أسباب خلل مكون المستندة إلى فئة
لماذا يتصرف المكون المستند إلى الفصل بهذه الطريقة؟ لفهم هذا ، دعونا نلقي نظرة على طريقة
showMessage
في
showMessage
:
class ProfilePage extends React.Component { showMessage = () => { alert('Followed ' + this.props.user); };
هذه الطريقة تقرأ البيانات من
this.props.user
. الخصائص في React غير قابلة للتغيير ، لذلك لا تتغير. ومع ذلك ،
this
، كما هو الحال دائمًا ، كيان قابل للتغيير.
في الواقع ، يكمن الغرض من وجود
this
في الفصل في قدرة
this
على التغيير. تنفذ مكتبة React نفسها
this
الطفرات بشكل دوري ، مما يسمح بالعمل مع أحدث الإصدارات من طريقة
render
وطرق دورة حياة المكون.
نتيجةً لذلك ، إذا تمت إعادة عرض المكون الخاص بنا أثناء تنفيذ الطلب ، فسيتم تغيير
this.props
. بعد ذلك ،
showMessage
طريقة
showMessage
بقراءة قيمة
user
من كيان
props
"الجديد جدًا".
يسمح لك هذا بإجراء ملاحظة مثيرة للاهتمام فيما يتعلق بواجهات المستخدم. إذا قلنا أن واجهة المستخدم ، من الناحية النظرية ، هي وظيفة للحالة الحالية للتطبيق ، فإن معالجات الأحداث جزء من نتائج العرض - تمامًا مثل نتائج العرض المرئية. "ينتمي" معالجات الأحداث لدينا إلى عملية تقديم محددة إلى جانب خصائص وحالة محددة.
ومع ذلك ، فإن جدولة مهلة يقرأ رد الاتصال
this.props
ينتهك هذا الاتصال.
showMessage
showMessage ليس "مرتبطًا" بأي عملية تجسيد معينة ؛ ونتيجة لذلك ، "يفقد" الخصائص الصحيحة. قراءة البيانات من
this
فواصل هذا الصدد.
كيف ، عن طريق المكونات القائمة على الفصل ، لحل المشكلة؟
تخيل أنه لا توجد مكونات وظيفية في React. كيف إذن لحل هذه المشكلة؟
نحتاج إلى بعض الآلية "لاستعادة" الاتصال بين طريقة
render
بالخصائص الصحيحة واستدعاء showMessage ، الذي يقرأ البيانات من الخصائص. يجب أن تكون هذه الآلية موجودة في مكان ما حيث يتم فقدان جوهر
props
مع البيانات الصحيحة.
إحدى الطرق للقيام بذلك هي قراءة
this.props
مقدمًا في معالج الأحداث ، ثم تمرير ما تمت قراءته بوضوح إلى وظيفة رد الاتصال المستخدمة في
setTimeout
:
class ProfilePage extends React.Component { showMessage = (user) => { alert('Followed ' + user); }; handleClick = () => { const {user} = this.props; setTimeout(() => this.showMessage(user), 3000); }; render() { return <button onClick={this.handleClick}>Follow</button>; } }
هذا النهج هو
العمل . لكن الإنشاءات الإضافية المستخدمة هنا ، بمرور الوقت ، ستؤدي إلى زيادة في حجم الكود وإلى زيادة احتمال حدوث أخطاء فيه. ماذا لو كنا بحاجة إلى أكثر من خاصية واحدة؟ ماذا لو كنا بحاجة أيضا للعمل مع الدولة؟ إذا كانت طريقة
showMessage
طريقة أخرى وتقرأ هذه الطريقة
this.props.something
أو
this.state.something
،
this.state.something
مجددًا نفس المشكلة. ولكي نحلها ، يجب أن نمرر هذا
this.props
و
this.state
كحجج لجميع الأساليب التي تم استدعاؤها من
showMessage
.
إذا كان هذا صحيحًا ، فسوف يدمر كل وسائل الراحة التي يوفرها استخدام المكونات على أساس الفصول الدراسية. من الصعب تذكر حقيقة أنك تحتاج إلى التعامل مع الأساليب بهذه الطريقة ، فمن الصعب أتمتة ، نتيجة لذلك ، يوافق المطورون غالبًا ، بدلاً من استخدام أساليب مماثلة ، على وجود أخطاء في مشاريعهم.
وبالمثل ، فإن تضمين رمز
alert
في
handleClick
لا يحل مشكلة أكثر عالمية. نحتاج إلى هيكلة الكود بحيث يمكن تقسيمه إلى العديد من الطرق ، ولكن أيضًا حتى نتمكن من قراءة الخصائص والحالة التي تتوافق مع عملية العرض المرتبطة بمكالمة معينة. هذه المشكلة ، بالمناسبة ، لا تنطبق حتى حصرا على رد الفعل. يمكنك تشغيله في أي مكتبة لتطوير واجهات المستخدم ، مما يضع البيانات في كائنات قابلة للتغيير مثل
this
.
ربما من أجل حل هذه المشكلة ، يمكنك ربط الطرق في
this
المنشئ؟
class ProfilePage extends React.Component { constructor(props) { super(props); this.showMessage = this.showMessage.bind(this); this.handleClick = this.handleClick.bind(this); } showMessage() { alert('Followed ' + this.props.user); } handleClick() { setTimeout(this.showMessage, 3000); } render() { return <button onClick={this.handleClick}>Follow</button>; } }
لكن هذا لا يحل مشكلتنا. تذكر أننا نقرأ البيانات من
this.props
فوات الأوان ، وليس في بناء الجملة المستخدمة! ومع ذلك ، سيتم حل هذه المشكلة إذا كنا نعتمد على إغلاق JavaScript.
غالبًا ما يحاول المطورون تجنب الإغلاق ، لأنه
ليس من السهل التفكير في القيم التي لا يمكن أن تتغير مع مرور الوقت. لكن الخصائص في React غير قابلة للتغيير! (أو ، على الأقل ، ينصح بهذا بشدة). يسمح لك هذا بالتوقف عن إدراك عمليات الإغلاق كشيء يمكن للمبرمج ، كما يقولون ، "إطلاق النار على قدمه".
هذا يعني أنه في حالة "قفل" الخصائص أو حالة عملية تقديم معينة في الإغلاق ، يمكنك دائمًا الاعتماد عليها في عدم التغيير.
class ProfilePage extends React.Component { render() {
كما ترون ، نحن هنا "استحوذنا" على الخصائص أثناء استدعاء طريقة العرض.
الخصائص التي تم التقاطها بواسطة تقديم المكالمةباستخدام هذا الأسلوب ، يتم ضمان أي رمز في طريقة
render
(بما في ذلك
showMessage
) لرؤية الخصائص الملتقطة أثناء مكالمة معينة لهذه الطريقة. نتيجة لذلك ، لن يكون بإمكان React منعنا من القيام بما نحتاج إليه.
في طريقة
render
، يمكنك وصف أكبر عدد تريده من الوظائف المساعدة وكلها ستكون قادرة على استخدام الخصائص والحالة "الملتقطة". هذه هي الطريقة التي حل الإغلاقات مشكلتنا.
تحليل الحل للمشكلة باستخدام الإغلاق
ما وصلنا إليه للتو يسمح لنا
بحل المشكلة ، لكن هذا الرمز يبدو غريباً. لماذا هناك حاجة لفئة على الإطلاق إذا تم الإعلان عن الوظائف داخل طريقة
render
، وليس كطرق صفية؟
في الواقع ، يمكننا تبسيط هذا الرمز من خلال التخلص من "الصدفة" في شكل فئة تحيط بها:
function ProfilePage(props) { const showMessage = () => { alert('Followed ' + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( <button onClick={handleClick}>Follow</button> ); }
هنا ، كما في المثال السابق ، يتم التقاط الخصائص في الوظيفة ، لأن React يمررها إليها كوسيطة. على عكس
this
، React لا
props
كائن
props
.
يصبح هذا أكثر وضوحًا قليلاً إذا
props
تدمير
props
في إعلان الوظيفة:
function ProfilePage({ user }) { const showMessage = () => { alert('Followed ' + user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( <button onClick={handleClick}>Follow</button> ); }
عندما
ProfilePage
المكون الأصل
ProfilePage
مع خصائص أخرى ، سوف React استدعاء دالة
ProfilePage
. ولكن معالج الأحداث الذي تم استدعاؤه بالفعل ينتمي إلى المكالمة السابقة لهذه الوظيفة ، وتستخدم هذه المكالمة قيمة
user
الخاصة به واستدعاء showMessage الخاص به ، والذي يقرأ هذه القيمة. كل هذا لا يزال دون تغيير.
هذا هو السبب في الإصدار
الأصلي من مثالنا ، عند العمل مع مكون وظيفي ، لا يؤدي تغيير ملف تعريف آخر بعد النقر فوق الزر المقابل قبل عرض الرسالة إلى تغيير أي شيء. إذا تم تحديد ملف تعريف
Sophie
قبل النقر فوق الزر ، فسيتم عرض
'Followed Sophie'
في نافذة الرسالة ، بغض النظر عما يحدث.
باستخدام مكون وظيفيهذا السلوك صحيح (قد ترغب أيضًا في الاشتراك في
Sunil بالمناسبة ).
الآن اكتشفنا الفرق الكبير بين الوظائف والطبقات في React. كما ذكرنا سابقًا ، نحن نتحدث عن حقيقة أن المكونات الوظيفية تلتقط القيم. الآن دعونا نتحدث عن السنانير.
الغياب
عند استخدام الخطافات ، يمتد مبدأ "التقاط القيم" إلى الحالة. النظر في المثال التالي:
function MessageThread() { const [message, setMessage] = useState(''); const showMessage = () => { alert('You said: ' + message); }; const handleSendClick = () => { setTimeout(showMessage, 3000); }; const handleMessageChange = (e) => { setMessage(e.target.value); }; return ( <> <input value={message} onChange={handleMessageChange} /> <button onClick={handleSendClick}>Send</button> </> ); }
→
هنا يمكنك تجربة معه
على الرغم من أن هذا ليس مثالًا مثاليًا لواجهة تطبيق المراسلة ، فإن هذا المشروع يوضح نفس الفكرة: إذا أرسل المستخدم رسالة ، فلا ينبغي الخلط بين المكون والرسالة التي تم إرسالها. يلتقط ثابت
message
لهذا المكون الوظيفي الحالة التي "تنتمي" للمكون الذي يجعل المستعرض معالج النقر للزر الذي يستدعيه. نتيجة لذلك ، تخزن
message
ما كان في حقل الإدخال في وقت النقر فوق الزر
Send
.
مشكلة التقاط الخصائص والحالات عن طريق المكونات الوظيفية
نحن نعلم أن المكونات الوظيفية في React ، بشكل افتراضي ، تلتقط الخصائص والحالة. ولكن ماذا لو احتجنا إلى قراءة أحدث البيانات من الخصائص أو الحالات التي لا تنتمي إلى استدعاء دالة معين؟ ماذا لو أردنا "
قراءتها من المستقبل "؟
في المكونات القائمة على الفصل ، يمكن القيام بذلك ببساطة عن طريق الإشارة إلى
this.props
أو
this.state
، نظرًا لأن
this
كيان قابل للتغيير. تغيير لها تشارك في رد الفعل. يمكن أن تعمل المكونات الوظيفية أيضًا مع قيم قابلة للتغيير يتم مشاركتها بواسطة جميع المكونات. تسمى هذه القيم
ref
:
function MyComponent() { const ref = useRef(null);
ومع ذلك ، يحتاج مبرمج لإدارة هذه القيم بشكل مستقل.
يلعب جوهر
ref
نفس
دور حقول مثيل من الفصل. هذا هو "مخرج الطوارئ" في عالم حتمية قابلة للتغيير. قد تكون على دراية بمفهوم DOM DOM ، لكن هذه الفكرة أكثر عمومية. يمكن مقارنتها بصندوق يستطيع فيه المبرمج وضع شيء ما.
حتى خارجيا ، فإن بناء هذا الشكل.
this.something
يشبه صورة طبق الأصل من بناء
something.current
. إنهم يمثلون نفس المفهوم.
بشكل افتراضي ، لا يقوم React بإنشاء كيانات
ref
في المكونات الوظيفية لأحدث خاصية أو قيم الحالة. في كثير من الحالات ، لن تحتاج إليها ، وسيكون إنشائها التلقائي مضيعة للوقت. ومع ذلك ، يمكن تنظيم العمل معهم ، إذا لزم الأمر ، بمفردهم:
function MessageThread() { const [message, setMessage] = useState(''); const latestMessage = useRef(''); const showMessage = () => { alert('You said: ' + latestMessage.current); }; const handleSendClick = () => { setTimeout(showMessage, 3000); }; const handleMessageChange = (e) => { setMessage(e.target.value); latestMessage.current = e.target.value; };
إذا قرأنا
message
في
showMessage
،
showMessage
الرسالة التي كانت في الحقل وقت النقر فوق الزر
Send
. ولكن إذا كنت تقرأ
latestMessage.current
، يمكنك الحصول على أحدث قيمة - حتى إذا واصلنا إدخال النص في الحقل بعد النقر فوق الزر "
Send
.
يمكنك مقارنة
هذا وهذه الأمثلة من أجل تقييم الفرق بشكل مستقل. قيمة
ref
هي وسيلة "لتفادي" توحيد التقديم ، وفي بعض الحالات قد تكون مفيدة للغاية.
بشكل عام ، يجب تجنب قراءة أو كتابة قيم
ref
أثناء عملية التقديم لأن هذه القيم قابلة للتغيير. ونحن نسعى جاهدين لجعل تقديم يمكن التنبؤ بها. ومع ذلك ، إذا كنا بحاجة إلى الحصول على أحدث قيمة لشيء مخزّن في الخصائص أو في الحالة ، فإن تحديث قيمة
ref
يدويًا قد يكون مهمة شاقة. يمكن أن يكون آليا باستخدام التأثير:
function MessageThread() { const [message, setMessage] = useState('');
→
هنا مثال يستخدم هذا الرمز
نقوم بتعيين قيمة داخل التأثير ، ونتيجة لذلك ، لن تتغير قيمة
ref
إلا بعد تحديث DOM. هذا يضمن أن طفرة لدينا لا يعطل ميزات مثل
Time Slicing and Suspense ، والتي تعتمد على استمرارية عمليات التقديم.
في كثير من الأحيان لا يكون استخدام قيمة
ref
بهذه الطريقة. عادة ما يكون التقاط الخصائص أو الحالات نمطًا أفضل بكثير لسلوك النظام القياسي. ومع ذلك ، يمكن أن يكون هذا مناسبًا عند العمل مع
واجهات برمجة التطبيقات الضرورية ، مثل تلك التي تستخدم فترات أو اشتراكات. تذكر أنه يمكنك العمل بهذه الطريقة مع أي قيمة - مع الخصائص ، مع المتغيرات المخزنة في الحالة ، مع كائن
props
بأكمله
props
أو حتى مع وظيفة.
هذا النمط ، بالإضافة إلى ذلك ، قد يكون مفيدًا لأغراض التحسين. على سبيل المثال ، عندما يتغير شيء مثل
useCallback
كثيرًا. صحيح أن
الحل المفضل هو استخدام المخفض .
النتائج
في هذه المقالة ، نظرنا إلى أحد الأنماط الخاطئة لاستخدام المكونات المستندة إلى الفصل وتحدثنا عن كيفية حل هذه المشكلة بالإغلاق. ومع ذلك ، قد تلاحظ أنه عند محاولة تحسين الخطافات عن طريق تحديد مجموعة من التبعيات ، فقد تواجه أخطاء تتعلق بالإغلاقات القديمة. هل هذا يعني أن العيوب بحد ذاتها مشكلة. لا أعتقد ذلك.
كما هو موضح أعلاه ، فإن عمليات الإغلاق ، في الواقع ، تساعدنا في إصلاح المشكلات الصغيرة التي يصعب ملاحظتها. كما أنها تسهل كتابة التعليمات البرمجية التي تعمل بشكل صحيح في نفس
الوقت . هذا ممكن بسبب حقيقة أن الخصائص والحالة التي تم تقديم هذا المكون بها "مكون" داخل المكون.
في جميع الحالات التي رأيتها حتى الآن ، حدثت مشكلة "الإغلاقات القديمة" بسبب الافتراض الخاطئ بأن "الوظائف لا تتغير" ، أو أن "الخصائص تظل دائمًا كما هي". آمل أنه بعد قراءة هذه المادة ، فأنت مقتنع بأن هذا ليس كذلك.
وظائف "التقاط" خصائصها وحالتها - وبالتالي فهم أي وظائف هي في السؤال المهم أيضا. هذا ليس خطأ ؛ إنه ميزة للمكونات الوظيفية. لا ينبغي استبعاد الوظائف من "مجموعة التبعيات" للاستخدام أو
useCalback
، على سبيل المثال. (عادةً ما يكون الحل المناسب للمشكلة هو إما استخدام
useReducer
أو
useRef
. تحدثنا عن هذا أعلاه ، وسنقوم قريبًا بإعداد المواد التي ستكرس لاختيار النهج)
إذا كانت معظم الشفرة في تطبيقاتنا تعتمد على مكونات وظيفية ، فهذا يعني أننا بحاجة إلى معرفة المزيد عن
تحسين الشفرة ، وما هي القيم التي قد
تتغير مع مرور الوقت.
: « , , , , , ».
. , React , . , « », . , React .
, , .
React —