لهذا السبب نحتاج إلى ربط معالجات الأحداث في Class Components في React

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

class Foo extends React.Component{ constructor( props ){ super( props ); this.handleClick = this.handleClick.bind(this); } handleClick(event){ // your event handling logic } render(){ return (  Click Me  ); } } ReactDOM.render( , document.getElementById("app") );

في هذه المقالة ، سنكتشف سبب حاجتنا للقيام بذلك.

أوصي بالقراءة .bind()هنا إذا كنت لا تعرف بالفعل ما يفعله.

إلقاء اللوم على جافا سكريبت ، وليس الرد

حسنًا ، إلقاء اللوم يبدو قاسيًا بعض الشيء. هذا ليس شيئًا نحتاج إلى القيام به بسبب طريقة عمل React أو بسبب JSX. هذا بسبب الطريقة التي thisيعمل بها الربط في JavaScript.

لنرى ماذا سيحدث إذا لم نربط طريقة معالج الحدث بمثيل المكون الخاص بها:

class Foo extends React.Component{ constructor( props ){ super( props ); } handleClick(event){ console.log(this); // 'this' is undefined } render(){ return (  Click Me  ); } } ReactDOM.render( , document.getElementById("app") );

إذا قمت بتشغيل هذا الرمز ، فانقر فوق الزر "Click Me" وتحقق من وحدة التحكم الخاصة بك. سترى undefinedمطبوعًا على وحدة التحكم كقيمة thisمن داخل طريقة معالج الحدث. و handleClick()يبدو أن الطريقة قد فقدت سياقها (مثيل مكون) أو thisقيمة.

كيف يعمل هذا الربط في JavaScript

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

ولكن فيما يتعلق بمناقشتنا هنا ، فإن قيمة thisداخل دالة تعتمد على كيفية استدعاء هذه الوظيفة.

الربط الافتراضي

function display(){ console.log(this); // 'this' will point to the global object } display(); 

هذه مكالمة دالة عادي. قيمة thisداخل display()الطريقة في هذه الحالة هي النافذة - أو الكائن العام - في الوضع غير المقيد. في الوضع المقيد ، thisالقيمة هي undefined.

الربط الضمني

var obj = { name: 'Saurabh', display: function(){ console.log(this.name); // 'this' points to obj } }; obj.display(); // Saurabh 

عندما نسمي دالة بهذه الطريقة - مسبوقة بكائن سياق - يتم تعيين thisالقيمة الداخلية display()على obj.

ولكن عندما نقوم بتعيين مرجع الوظيفة هذا إلى متغير آخر واستدعاء الوظيفة باستخدام مرجع الوظيفة الجديد هذا ، نحصل على قيمة مختلفة thisللداخل display().

var name = "uh oh! global"; var outerDisplay = obj.display; outerDisplay(); // uh oh! global

في المثال أعلاه ، عندما نستدعي outerDisplay()، لا نحدد كائن سياق. إنها استدعاء دالة عادي بدون كائن مالك. في هذه الحالة ، تعود قيمة thisالداخل display()إلى الارتباط الافتراضي . يشير إلى الكائن العام أو undefinedإذا كانت الوظيفة التي يتم استدعاؤها تستخدم الوضع المتشدد.

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

ضع في اعتبارك setTimeoutالتعريف الوهمي كما هو موضح أدناه ، ثم قم باستدعاؤه.

// A dummy implementation of setTimeout function setTimeout(callback, delay){ //wait for 'delay' milliseconds callback(); } setTimeout( obj.display, 1000 );

يمكننا معرفة أنه عند الاتصال setTimeout، تقوم JavaScript داخليًا بتخصيص obj.displayحجتها callback.

callback = obj.display;

تؤدي عملية الإسناد هذه ، كما رأينا من قبل ، display()إلى فقدان الوظيفة لسياقها. عندما يتم استدعاء رد النداء هذا في النهاية setTimeout، تعود thisالقيمة الداخلية display()إلى الارتباط الافتراضي .

var name = "uh oh! global"; setTimeout( obj.display, 1000 ); // uh oh! global

صريح ملزم

لتجنب هذا، يمكننا من الصعب صراحة ربط على thisقيمة وظيفة باستخدام bind()الأسلوب.

var name = "uh oh! global"; obj.display = obj.display.bind(obj); var outerDisplay = obj.display; outerDisplay(); // Saurabh

الآن ، عندما نتصل outerDisplay()، فإن قيمة thisالنقاط objللداخل display().

حتى إذا مررنا obj.displayعلى شكل رد اتصال ، فإن thisالقيمة الموجودة بالداخل display()ستشير إلى obj.

إعادة إنشاء السيناريو باستخدام JavaScript فقط

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

كما ذكرت وشرحت ، هذا بسبب الطريقة التي thisيعمل بها الربط في JavaScript وليس بسبب طريقة عمل React. لذلك دعونا نزيل الكود الخاص بـ React وننشئ مثال JavaScript خالصًا مشابهًا لمحاكاة هذا السلوك.

class Foo { constructor(name){ this.name = name } display(){ console.log(this.name); } } var foo = new Foo('Saurabh'); foo.display(); // Saurabh // The assignment operation below simulates loss of context // similar to passing the handler as a callback in the actual // React Component var display = foo.display; display(); // TypeError: this is undefined

نحن لا نحاكي الأحداث والمعالجات الفعلية ، لكننا بدلاً من ذلك نستخدم رمزًا مترادفًا. كما لاحظنا في مثال مكون React ، كانت thisالقيمة undefinedكما تم فقد السياق بعد تمرير المعالج باعتباره رد اتصال - مرادف لعملية الإسناد. هذا ما نلاحظه هنا أيضًا في مقتطف JavaScript غير المتفاعل.

"انتظر دقيقة! ألا يجب أن thisتشير القيمة إلى الكائن العام ، لأننا نقوم بتشغيل هذا في الوضع غير المقيد وفقًا لقواعد الربط الافتراضي؟ " ربما تسال.

لا. هذا هو السبب:

جثث الإعلانات الطبقة و تعبيرات الطبقة يتم تنفيذها في الوضع الصارم، وهذا هو منشئ الطرق، ثابت والنموذج. يتم تنفيذ وظائف Getter و setter في الوضع المقيد.

يمكنك قراءة المقال كاملا هنا.

لذلك ، لمنع الخطأ ، نحتاج إلى ربط thisالقيمة مثل هذا:

class Foo { constructor(name){ this.name = name this.display = this.display.bind(this); } display(){ console.log(this.name); } } var foo = new Foo('Saurabh'); foo.display(); // Saurabh var display = foo.display; display(); // Saurabh

لا نحتاج إلى القيام بذلك في المُنشئ ، ويمكننا القيام بذلك في مكان آخر أيضًا. ضع في اعتبارك هذا:

class Foo { constructor(name){ this.name = name; } display(){ console.log(this.name); } } var foo = new Foo('Saurabh'); foo.display = foo.display.bind(foo); foo.display(); // Saurabh var display = foo.display; display(); // Saurabh

لكن المُنشئ هو المكان الأمثل والأكثر فاعلية لتشفير عبارات ربط معالج الأحداث ، مع الأخذ في الاعتبار أن هذا هو المكان الذي تحدث فيه كل التهيئة.

لماذا لا نحتاج إلى ربط " this’وظائف Arrow؟

لدينا طريقتان أخريان يمكننا من خلالها تحديد معالجات الأحداث داخل مكون React.

  • صيغة حقول الفصل العام (تجريبي)
class Foo extends React.Component{ handleClick = () => { console.log(this); } render(){ return (  Click Me  ); } } ReactDOM.render( , document.getElementById("app") );
  • وظيفة السهم في رد الاتصال
class Foo extends React.Component{ handleClick(event){ console.log(this); } render(){ return (  this.handleClick(e)}> Click Me  ); } } ReactDOM.render( , document.getElementById("app") );

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

والسبب هو أنه في حالة وظائف السهم ، thisيتم ربطه معجمياً . هذا يعني أنه يستخدم سياق دالة التضمين - أو النطاق العام - كقيمة لها this.

In the case of the public class fields syntax example, the arrow function is enclosed inside the Foo class — or constructor function — so the context is the component instance, which is what we want.

In the case of the arrow function as callback example, the arrow function is enclosed inside the render() method, which is invoked by React in the context of the component instance. This is why the arrow function will also capture this same context, and the this value inside it will properly point to the component instance.

For more details regarding lexical this binding, check out this excellent resource.

To make a long story short

In Class Components in React, when we pass the event handler function reference as a callback like this

Click Me

يفقد أسلوب معالج الحدث سياقه المرتبط ضمنيًا . عند حدوث الحدث واستدعاء المعالج ، thisتعود القيمة إلى الارتباط الافتراضي ويتم تعيينها على undefined، حيث تعمل عمليات تعريف الفئة وطرق النموذج الأولي في الوضع المقيد.

عندما نربط thisمعالج الحدث بمثيل المكون في المُنشئ ، يمكننا تمريره على أنه رد اتصال دون القلق بشأن فقده لسياقه.

تُستثنى وظائف الأسهم من هذا السلوك لأنها تستخدم الربط المعجمي الذي يربطها تلقائيًا بالنطاق الذي تم تعريفها فيه.this