ننشر اليوم ترجمة للمواد ، يقدم مؤلفها ، بعد تحليل ميزات العمل مع الكائنات في JavaScript ، لمطوري React منهجية لتسريع التطبيقات. على وجه الخصوص ، نحن نتحدث عن حقيقة أن المتغير ، الذي ، كما يقولون ، "يتم تعيين كائن" ، والذي يطلق عليه ببساطة "كائن" ، في الواقع ، لا يخزن الكائن نفسه ، ولكنه رابط إليه. الوظائف في JavaScript هي أيضًا كائنات ، لذا فإن ما سبق صحيح بالنسبة لها. مع وضع ذلك في الاعتبار ، يمكن أن يؤدي تصميم مكونات التفاعل والتحليل النقدي لرموزها إلى تحسين آلياتها الداخلية وتحسين أداء التطبيق.
ميزات العمل مع الكائنات في JavaScript
إذا قمت بإنشاء بعض الوظائف التي تبدو متشابهة تمامًا وحاولت مقارنتها ، فقد اتضح أنها مختلفة من وجهة نظر النظام. للتحقق من ذلك ، يمكنك تنفيذ الكود التالي:
const functionOne = function() { alert('Hello world!'); }; const functionTwo = function() { alert('Hello world!'); }; functionOne === functionTwo;
الآن دعنا نحاول تعيين متغير لوظيفة موجودة تم تعيينها بالفعل لمتغير آخر ، ومقارنة هذين المتغيرين:
const functionThree = function() { alert('Hello world!'); }; const functionFour = functionThree; functionThree === functionFour;
كما ترى ، مع هذا النهج ، فإن عامل المساواة الصارم يعود
true
.
تتصرف الكائنات بشكل طبيعي بنفس الطريقة:
const object1 = {}; const object2 = {}; const object3 = object1; object1 === object2; // false object1 === object3; // true
هنا نتحدث عن JavaScript ، ولكن إذا كانت لديك خبرة في التطوير بلغات أخرى ، فقد تكون على دراية بمفهوم المؤشرات. في الرمز أعلاه ، في كل مرة يتم إنشاء كائن ، يتم تخصيص جزء من ذاكرة النظام له. عندما نستخدم أمرًا في النموذج
object1 = {}
، فإن هذا يؤدي إلى ملء جزء من الذاكرة مع بعض البيانات المخصصة خصيصًا
object1
.
من الممكن أن نتخيل
object1
أنه العنوان الذي توجد به هياكل البيانات المتعلقة بالكائن في الذاكرة. يؤدي تنفيذ الأمر
object2 = {}
إلى تخصيص مساحة ذاكرة أخرى ، مصممة خصيصًا
object2
. هل يوجد
obect1
و
object2
في نفس منطقة الذاكرة؟ لا ، لكل منهم مؤامرة خاصة به. لهذا عندما نحاول مقارنة
object1
و
object2
نحصل على
false
. قد يكون لهذه الكائنات بنية متطابقة ، ولكن العناوين في الذاكرة التي توجد فيها تختلف ، وهي العناوين التي يتم فحصها أثناء المقارنة.
من خلال تنفيذ الأمر
object3 = object1
، نكتب عنوان
object1
في ثابت
object3
. هذا ليس كائن جديد. يتم تعيين هذا الثابت عنوان كائن موجود. يمكنك التحقق من ذلك عن طريق:
const object1 = { x: true }; const object3 = object1; object3.x = false; object1.x;
في هذا المثال ، يتم إنشاء كائن في الذاكرة ويتم كتابة عنوانه إلى
object1
الثابت
object1
. ثم يتم كتابة نفس العنوان إلى
object3
الثابت
object3
. يؤدي تغيير الكائن 3 إلى تغيير الكائن في الذاكرة. هذا يعني أنه عند الوصول إلى كائن باستخدام أي مرجع آخر له ، على سبيل المثال ،
object1
المخزن في
object1
، سنعمل بالفعل مع نسخته المعدلة.
الوظائف والأشياء والتفاعل
غالبًا ما يؤدي سوء فهم الآلية المذكورة أعلاه من قبل المطورين المبتدئين إلى أخطاء ، وربما يستحق النظر في ميزات العمل مع الكائنات مقالة منفصلة. ومع ذلك ، فإن موضوعنا اليوم هو أداء تطبيقات React. في هذه المنطقة ، يمكن ارتكاب الأخطاء حتى من قبل المطورين ذوي الخبرة إلى حد ما الذين لا يهتمون ببساطة بكيفية تأثر تطبيقات التفاعل بحقيقة أن متغيرات جافا سكريبت والثوابت لا يتم تخزينها في الكائنات نفسها ، ولكن فقط الروابط إليها.
ما علاقة هذا بالتفاعل؟ لدى React آليات ذكية لحفظ موارد النظام تهدف إلى تحسين أداء التطبيق: إذا لم تتغير خصائص وحالة المكون ، فما الذي لا تتغير وظيفة
render
. من الواضح أنه إذا ظل المكون كما هو ، فلا حاجة إلى إعادة عرضه. إذا لم يتغير شيء ،
render
وظيفة
render
كما كانت من قبل ، لذلك ليست هناك حاجة لتنفيذه. هذه الآلية تجعل رد فعل سريع. يتم عرض شيء فقط عند الضرورة.
يقوم React بفحص خصائص وحالة المكونات من أجل المساواة باستخدام ميزات JavaScript القياسية ، أي أنه يقارنها ببساطة باستخدام عامل
==
. لا تقوم شركة React بإجراء مقارنة "ضحلة" أو "عميقة" للأشياء من أجل تحديد مساواتها. "المقارنة الضحلة" هو مفهوم يستخدم لوصف مقارنة بين كل زوج من قيم المفاتيح الرئيسية لكائن ما ، على عكس المقارنة التي تتم فيها مقارنة عناوين الكائنات في الذاكرة فقط (المراجع إليها). تذهب المقارنة "العميقة" للكائنات إلى أبعد من ذلك ، وإذا كانت قيم الخصائص المقارنة للكائنات هي أيضًا كائنات ، فإنها تقارن أيضًا أزواج القيمة الرئيسية لهذه الكائنات. تتكرر هذه العملية لجميع الكائنات المتداخلة في كائنات أخرى. رد الفعل لا يفعل شيئًا من هذا النوع ، فقط التحقق من المساواة في الروابط.
إذا قمت ، على سبيل المثال ، بتغيير خاصية مكون ممثلة بكائن من النموذج
{ x: 1 }
إلى كائن آخر يبدو متشابهًا تمامًا ، فستقوم React بإعادة عرض المكون ، نظرًا لأن هذه الكائنات موجودة في مناطق ذاكرة مختلفة. إذا كنت تتذكر المثال أعلاه ، فعند تغيير خصائص المكون من
object1
إلى
object3
، لن يقوم
object3
بإعادة عرض هذا المكون ، لأن الثوابت
object1
و
object3
تشير إلى نفس الكائن.
يتم تنظيم العمل مع الوظائف في JavaScript بنفس الطريقة تمامًا. إذا واجهت React نفس الميزات التي تختلف عناوينها ، فسيتم إعادة عرضها. إذا كانت "الوظيفة الجديدة" مجرد رابط إلى دالة تم استخدامها بالفعل ، فلن تكون هناك إعادة للعرض.
مشكلة نموذجية عند العمل مع المكونات
في ما يلي أحد سيناريوهات العمل مع المكونات ، والتي ، للأسف ، تصادفني باستمرار عند التحقق من رمز شخص آخر:
class SomeComponent extends React.PureComponent { get instructions() { if (this.props.do) { return 'Click the button: '; } return 'Do NOT click the button: '; } render() { return ( <div> {this.instructions} <Button onClick={() => alert('!')} /> </div> ); } }
أمامنا مكون بسيط للغاية. إنه زر ، عند النقر عليه ، يتم عرض إشعار. بجوار الزر يتم عرض التعليمات الخاصة باستخدامه ، وإبلاغ المستخدم ما إذا كان يجب الضغط على هذا الزر. تحكم في التعليمات التي سيتم عرضها عن طريق تعيين
SomeComponent
do
(
do={true}
أو
do={false}
)
SomeComponent
.
في كل مرة يتم فيها إعادة عرض مكون
SomeComponent
(عندما يتم تغيير قيمة خاصية
do
من
true
إلى
false
والعكس صحيح) ، يتم أيضًا عرض عنصر
Button
. يُعاد إنشاء معالج
onClick
، على الرغم من أنه دائمًا ما يكون هو نفسه ، في كل مرة يتم فيها استدعاء وظيفة
render
. ونتيجة لذلك ، اتضح أنه في كل مرة يتم عرض المكون في الذاكرة ، يتم إنشاء وظيفة جديدة ، حيث يتم إنشاؤها في وظيفة
render
، يتم تمرير رابط إلى العنوان الجديد في الذاكرة إلى
<Button />
، ويتم أيضًا عرض مكون
Button
مرة أخرى ، على الرغم من أنه في لم يتغير شيء على الإطلاق.
لنتحدث عن كيفية إصلاحه.
حل المشكلات
إذا كانت الوظيفة مستقلة عن المكون (
this
السياق) ، فيمكنك تعريفها خارج المكون. ستستخدم جميع مثيلات المكون نفس مرجع الوظيفة ، لأنه في جميع الحالات سيكون نفس الوظيفة. إليك ما يبدو عليه:
const createAlertBox = () => alert('!'); class SomeComponent extends React.PureComponent { get instructions() { if (this.props.do) { return 'Click the button: '; } return 'Do NOT click the button: '; } render() { return ( <div> {this.instructions} <Button onClick={createAlertBox} /> </div> ); } }
على عكس المثال السابق ،
createAlertBox
، مع كل استدعاء
createAlertBox
، على نفس الرابط إلى نفس المنطقة في الذاكرة. ونتيجة لذلك ، لن يتم تنفيذ مخرجات
Button
المتكررة.
على الرغم من أن مكون
Button
صغير وسريع العرض ، إلا أن المشكلة الموضحة أعلاه والمرتبطة بالإعلان الداخلي للوظائف يمكن العثور عليها أيضًا في المكونات الكبيرة والمعقدة التي تستغرق وقتًا طويلاً في العرض. هذا يمكن أن يبطئ بشكل كبير تطبيق رد الفعل. في هذا الصدد ، من المنطقي اتباع التوصية ، والتي بموجبها لا ينبغي أبدًا الإعلان عن هذه الوظائف داخل طريقة
render
.
إذا كانت الوظيفة تعتمد على المكون ، أي أنه لا يمكن تعريفها خارجها ، يمكن تمرير طريقة المكون كمعالج للأحداث:
class SomeComponent extends React.PureComponent { createAlertBox = () => { alert(this.props.message); }; get instructions() { if (this.props.do) { return 'Click the button: '; } return 'Do NOT click the button: '; } render() { return ( <div> {this.instructions} <Button onClick={this.createAlertBox} /> </div> ); } }
في هذه الحالة ، في كل مثيل لـ
SomeComponent
عند النقر فوق الزر ، سيتم عرض رسائل مختلفة. يجب أن يكون معالج الأحداث الخاص بعنصر
Button
فريدًا لـ
SomeComponent
. عند تمرير طريقة
cteateAlertBox
، لا يهم ما إذا كان
SomeComponent
إعادة تقديم
SomeComponent
. لا يهم إذا تم تغيير خاصية
message
.
createAlertBox
يتغير عنوان الدالة
createAlertBox
، مما يعني أنه لا يجب عرض عنصر
Button
مرة أخرى. بفضل هذا ، يمكنك حفظ موارد النظام وتحسين سرعة عرض التطبيق.
كل هذا جيد. ولكن ماذا لو كانت الوظائف ديناميكية؟
حل مشكلة أكثر تعقيدًا
يطلب منك كاتب هذه المادة الانتباه إلى حقيقة أنه قام بإعداد الأمثلة في هذا القسم ، مع أخذ أول ما يتبادر إلى ذهنه ، وهو مناسب لتوضيح إعادة استخدام الوظائف. تهدف هذه الأمثلة إلى مساعدة القارئ على فهم جوهر الفكرة. على الرغم من أن هذا القسم موصى به للقراءة لفهم جوهر ما يحدث ، ينصح المؤلف بالاهتمام بالتعليقات على المقالة الأصلية ، حيث اقترح بعض القراء إصدارات أفضل من الآليات التي تمت مناقشتها هنا ، والتي تأخذ في الاعتبار ميزات إبطال ذاكرة التخزين المؤقت وآليات إدارة الذاكرة المضمنة في React.لذلك ، من الشائع جدًا أن يوجد في مكون واحد العديد من معالجات الأحداث الديناميكية الفريدة ، على سبيل المثال ، يمكن رؤية شيء مشابه في الشفرة ، حيث يتم استخدام طريقة صفيف
map
في طريقة العرض:
class SomeComponent extends React.PureComponent { render() { return ( <ul> {this.props.list.map(listItem => <li key={listItem.text}> <Button onClick={() => alert(listItem.text)} /> </li> )} </ul> ); } }
هنا ، سيتم عرض عدد مختلف من الأزرار وسيتم إنشاء عدد مختلف من معالجات الأحداث ، كل منها يتم تمثيله بوظيفة فريدة ، وقبل
SomeComponent
إنشاء
SomeComponent
، لا يُعرف ما ستكون هذه الوظائف. كيف تحل هذا اللغز؟
هنا ستساعدنا عملية الحفظ ، أو ببساطة أكثر ، التخزين المؤقت. لكل قيمة فريدة ، قم بإنشاء دالة ووضعها في ذاكرة التخزين المؤقت. إذا حدثت هذه القيمة الفريدة مرة أخرى ، فسيكون كافياً أن تأخذ من ذاكرة التخزين المؤقت الوظيفة المقابلة لها ، والتي تم وضعها مسبقًا في ذاكرة التخزين المؤقت.
إليك ما يبدو عليه تنفيذ هذه الفكرة:
class SomeComponent extends React.PureComponent {
تتم معالجة كل عنصر من عناصر الصفيف بواسطة أسلوب
getClickHandler
. هذه الطريقة ، في أول مرة يتم استدعاؤها بقيمة معينة ، ستنشئ وظيفة فريدة لهذه القيمة ، ووضعها في ذاكرة التخزين المؤقت وإعادتها. ستؤدي جميع الاستدعاءات اللاحقة إلى هذه الطريقة ، والتي تمرر نفس القيمة إليها ، إلى إرجاع رابط إلى الوظيفة من ذاكرة التخزين المؤقت.
نتيجة لذلك ، لن تؤدي إعادة تقديم
SomeComponent
إلى إعادة عرض
Button
. وبالمثل ، ستؤدي إضافة عناصر إلى خاصية
list
إلى إنشاء معالجات الأحداث لكل زر بشكل ديناميكي.
ستحتاج إلى أن تكون مبدعًا في إنشاء معرفات فريدة للمعالجات إذا تم تعريفها بواسطة أكثر من متغير واحد ، ولكن هذا ليس أكثر تعقيدًا من الإنشاء المعتاد لخاصية
key
فريدة لكل كائن JSX تم الحصول عليه نتيجة لطريقة
map
.
هنا أود أن أحذرك من المشاكل المحتملة لاستخدام فهارس المصفوفة كمعرفات. والحقيقة هي أنه مع هذا النهج ، يمكن أن تواجه أخطاء إذا تغير ترتيب العناصر في الصفيف أو تم حذف بعض عناصره. لذلك ، على سبيل المثال ، إذا بدا في البداية صفيف مشابه مثل
[ 'soda', 'pizza' ]
، ثم تحول إلى
[ 'pizza' ]
، وقمت بتخزين معالجات الأحداث مؤقتًا باستخدام أمر
listeners[0] = () => alert('soda')
النماذج
listeners[0] = () => alert('soda')
، ستجد أنه عندما ينقر المستخدم على الزر الذي تم تعيين المعالج به المعرف 0 والذي يجب أن يعرض رسالة
pizza
، وفقًا لمحتويات المصفوفة
[ 'pizza' ]
، سيتم عرض رسالة
soda
. لنفس السبب ، لا يوصى باستخدام مؤشرات الصفيف كخصائص رئيسية.
الملخص
في هذه المقالة ، قمنا بفحص ميزات آليات JavaScript الداخلية ، مع الأخذ في الاعتبار أنه يمكنك تسريع عرض تطبيقات React. نأمل أن تكون الأفكار المقدمة هنا مفيدة.
أعزائي القراء! إذا كنت تعرف أي طرق مثيرة للاهتمام لتحسين تطبيقات React ، فيرجى مشاركتها.
