الفرق بين المراجعتين لصفحة: «React/handling events»

من موسوعة حسوب
أنشأ الصفحة ب'<noinclude>{{DISPLAYTITLE:معالجة الأحداث في React}}</noinclude>'
 
ط تحديث الصفحة، والعنوان، والتصنيفات
 
(3 مراجعات متوسطة بواسطة مستخدمين اثنين آخرين غير معروضة)
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:معالجة الأحداث في React}}</noinclude>
<noinclude>{{DISPLAYTITLE:معالجة الأحداث في React}}</noinclude>
تُشبه معالجة الأحداث لعناصر React معالجة الأحداث لعناصر DOM، ولكن هنالك فروق تتعلّق بالصياغة:
* تُسمَّى أحداث React باستخدام حالة الأحرف camelCase (أي عند وجود اسم مؤلف من عدة كلمات نجعل الحرف الأول من الكلمة الأولى بالشكل الصغير أمّا باقي الكلمات نجعل حرفها الأول بالشكل الكبير) بدلًا من استخدام الشكل الصغير للأحرف.
* نُمرِّر في JSX دالة كمُعالِج للأحداث، بدلًا من سلسلة نصيّة.
على سبيل المثال لنأخذ شيفرة HTML التالية:<syntaxhighlight lang="javascript">
<button onclick="activateLasers()">
  تفعيل الليزر
</button>
</syntaxhighlight>تكون الشيفرة السابقة مختلفة قليلًا في React:<syntaxhighlight lang="javascript">
<button onClick={activateLasers}>
  تفعيل الليزر
</button>
</syntaxhighlight>من الفروق الأخرى أنّه لا يمكنك إعادة القيمة <code>false</code> لمنع السلوك الافتراضي في React، بل يجب عليك أن تستدعي <code>preventDefault</code> بشكل صريح، فمثلًا في HTML لمنع السلوك الافتراضي للروابط في فتح صفحة جديدة بإمكانك كتابة ما يلي:<syntaxhighlight lang="html">
<a href="#" onclick="console.log('ضُغِط على الرابط بنجاح'); return false">
  اضغط هنا
</a>
</syntaxhighlight>أمّا في React فنكتب بدلًا من ذلك ما يلي:<syntaxhighlight lang="javascript">
function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('ضُغِط على الرابط بنجاح');
  }
  return (
    <a href="#" onClick={handleClick}>
      اضغط هنا
    </a>
  );
}
</syntaxhighlight>يُمثِّل المتغيّر <code>e</code> هنا حدثًا مُصطنعًا، حيث تُعرِّف React هذه الأحداث المُصطنعة وفق [https://www.w3.org/TR/DOM-Level-3-Events/ معايير W3C]، بحيث لا نهتم بمشاكل التوافقيّة بين المتصفحات. للمزيد حول الأحداث المصطنعة انتقل إلى مرجع [[React/events|الأحداث في React]].
ينبغي بشكل عام عند استخدام React ألّا تحتاج إلى استدعاء <code>addEventListener</code> لإضافة مُستمِع للأحداث إلى عنصر DOM بعد إنشائه، وبدلًا من ذلك نُضيف مُستمِعًا للأحداث عند تصيير العنصر (Rendering Element).
عند تعريف المُكوِّنات باستخدام [[JavaScript/class|الأصناف]] هنالك نمط شائع لمعالج الأحداث ليكون تابعًا ضمن الصنف، ففي المثال التالي يعرض المُكوِّن <code>Toggle</code> زرًّا يُتيح للمستخدم بأن يقلب بين الحالتين <code>"ON"</code> و <code>"OFF"</code>:<syntaxhighlight lang="javascript">
class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};
   
// يكون هذا الربط ضروريًّا لكي تعمل this في رد النداء
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }
  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}
ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);
</syntaxhighlight>[http://codepen.io/gaearon/pen/xEmzGg?editors=0010 جرِّب هذا المثال على موقع CodePen].
يجب أن تنتبه إلى معنى <code>this</code> في ردود نداء JSX، ففي JavaScript لا [[JavaScript/Function/bind|تُربَط]] توابع الصّنف بشكل افتراضي عن طريق التابع <code>bind()</code>‎، وإن نسيت أن تربط وتُمرِّر <code>this.handleClick</code> إلى <code>onClick</code>، فستكون قيمة <code>this</code> غير مُعرَّفة عند استدعاء الدالة.
لا يُعدُّ هذا سلوكًا مرتبطًا بـ React، بل هو جزء من [[JavaScript/this#.D8.B3.D9.8A.D8.A7.D9.82 .D8.A7.D9.84.D8.AF.D9.88.D8.A7.D9.84|سياق الدوال في JavaScript]]. بشكل عام إن أشرت إلى التابع بدون استخدام الأقواس <code>()</code> بعده، مثل <code>‎onClick={this.handleClick}</code>‎، فيجب أن تربط ذلك التابع.
إن كان استدعاء التابع [[JavaScript/this#.D8.A7.D9.84.D8.AF.D8.A7.D9.84.D8.A9 bind|bind()]]‎ يزعجك، فهناك طريقتان للالتفاف حول استعماله، إن كنت تستخدم [https://babeljs.io/docs/plugins/transform-class-properties/ صياغة حقول الصنف العامة] التجريبيّة فبإمكانك استخدام حقول الصنف لربط ردود النداء بشكل صحيح:<syntaxhighlight lang="javascript">
class LoggingButton extends React.Component {
  // تحرص هذه الصياغة على ربط this ضمن handleClick
  // تحذير: تعتبر هذه صياغة تجريبية experimental
  handleClick = () => {
    console.log('قيمة this هي:', this);
  }
  render() {
    return (
      <button onClick={this.handleClick}>
        اضغط هنا
      </button>
    );
  }
}
</syntaxhighlight>هذه الصياغة مُمكَّنة بشكل افتراضي عند استخدام الأمر [https://github.com/facebookincubator/create-react-app create-react-app].
إن لم تكن تستخدم صياغة حقول الأصناف، فبإمكانك استخدام [[JavaScript/this#.D8.A7.D9.84.D8.AF.D9.88.D8.A7.D9.84 .D8.A7.D9.84.D8.B3.D9.87.D9.85.D9.8A.D8.A9|الدوال السّهمية]] في ردود النداء:<syntaxhighlight lang="javascript">
class LoggingButton extends React.Component {
  handleClick() {
    console.log('قيمة this هي:', this);
  }
  render() {
// تحرص هذه الصياغة على ربط this ضمن handleClick
    return (
      <button onClick={(e) => this.handleClick(e)}>
        اضغط هنا
      </button>
    );
  }
}
</syntaxhighlight>المشكلة في هذه الصياغة هي إنشاء رد نداء مختلف في كل مرّة يُصيَّر فيها المُكوِّن <code>LoggingButton</code>، وفي معظم الحالات يكون هذا مقبولًا، ولكن إن مرَّرنا رد النداء هذا كخاصيّة <code>prop</code> إلى المُكوِّنات الموجودة في المستوى الأدنى، فقد تقوم هذه المُكوِّنات بعمل إعادة تصيير (re-rendering) إضافيّة. نوصي بشكل عام الربط في الدالة البانية (constructor) أو استخدام صياغة حقول الأصناف لتجنّب مثل هذا النوع من مشاكل الأداء.
== تمرير وسائط إلى معالجات الأحداث ==
من الشائع أن نحتاج بداخل الحلقات (loops) إلى تمرير مُعامِل إضافي إلى مُعالِج الأحداث، فمثلًا إن كان المتغيّر <code>id</code> يُمثِّل مُعرِّف الصف (row ID)، فسيعمل كلا السطرين التاليين بنفس الكفاءة:<syntaxhighlight lang="html">
<button onClick={(e) => this.deleteRow(id, e)}>حذف الصف</button>
<button onClick={this.deleteRow.bind(this, id)}>حذف الصف</button>
</syntaxhighlight>إنّ السطرين السابقين متكافئان ويستخدمان [[JavaScript/this#.D8.A7.D9.84.D8.AF.D9.88.D8.A7.D9.84 .D8.A7.D9.84.D8.B3.D9.87.D9.85.D9.8A.D8.A9|الدوال السهمية]] و [[JavaScript/this#.D8.A7.D9.84.D8.AF.D8.A7.D9.84.D8.A9 bind|Function.prototype.bind]] على التوالي وبالترتيب.
في كلتا الحالتين سيُمرَّر الوسيط <code>e</code> الذي يُمثِّل حدث React كوسيطٍ ثانٍ بعد المُعرِّف <code>ID</code>. في الدوال السهمية يجب أن نُمرِّره بشكلٍ صريح، ولكن في حالة استخدام التابع <code>bind</code> فستُمرَّر أي وسائط أخرى تلقائيًّا.
== انظر أيضًا ==
* [[React/hello world|مثال أهلًا بالعالم في React]]
* [[React/introducing jsx|مقدمة إلى JSX]]
* [[React/rendering elements|تصيير العناصر]]
* [[React/components and props|المكونات والخاصيات]]
* [[React/state and lifecycle|حالة ودورة حياة المكونات]]
* [[React/conditional rendering|التصيير الشرطي]]
* [[React/lists and keys|القوائم والمفاتيح]]
* [[React/forms|الحقول]]
* [[React/lifting state up|رفع الحالات المشتركة للمستوى الأعلى]]
* [[React/composition vs inheritance|الفرق بين التركيب والوراثة في React]]
* [[React/thinking in react|أسلوب التفكير في React]]
== مصادر ==
* [https://reactjs.org/docs/handling-events.html صفحة معالجة الأحداث في توثيق React الرسمي].
[[تصنيف:React]]
[[تصنيف:React Main Concepts]]

المراجعة الحالية بتاريخ 07:38، 23 فبراير 2019

تُشبه معالجة الأحداث لعناصر React معالجة الأحداث لعناصر DOM، ولكن هنالك فروق تتعلّق بالصياغة:

  • تُسمَّى أحداث React باستخدام حالة الأحرف camelCase (أي عند وجود اسم مؤلف من عدة كلمات نجعل الحرف الأول من الكلمة الأولى بالشكل الصغير أمّا باقي الكلمات نجعل حرفها الأول بالشكل الكبير) بدلًا من استخدام الشكل الصغير للأحرف.
  • نُمرِّر في JSX دالة كمُعالِج للأحداث، بدلًا من سلسلة نصيّة.

على سبيل المثال لنأخذ شيفرة HTML التالية:

<button onclick="activateLasers()">
  تفعيل الليزر
</button>

تكون الشيفرة السابقة مختلفة قليلًا في React:

<button onClick={activateLasers}>
  تفعيل الليزر
</button>

من الفروق الأخرى أنّه لا يمكنك إعادة القيمة false لمنع السلوك الافتراضي في React، بل يجب عليك أن تستدعي preventDefault بشكل صريح، فمثلًا في HTML لمنع السلوك الافتراضي للروابط في فتح صفحة جديدة بإمكانك كتابة ما يلي:

<a href="#" onclick="console.log('ضُغِط على الرابط بنجاح'); return false">
  اضغط هنا
</a>

أمّا في React فنكتب بدلًا من ذلك ما يلي:

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('ضُغِط على الرابط بنجاح');
  }

  return (
    <a href="#" onClick={handleClick}>
      اضغط هنا
    </a>
  );
}

يُمثِّل المتغيّر e هنا حدثًا مُصطنعًا، حيث تُعرِّف React هذه الأحداث المُصطنعة وفق معايير W3C، بحيث لا نهتم بمشاكل التوافقيّة بين المتصفحات. للمزيد حول الأحداث المصطنعة انتقل إلى مرجع الأحداث في React.

ينبغي بشكل عام عند استخدام React ألّا تحتاج إلى استدعاء addEventListener لإضافة مُستمِع للأحداث إلى عنصر DOM بعد إنشائه، وبدلًا من ذلك نُضيف مُستمِعًا للأحداث عند تصيير العنصر (Rendering Element).

عند تعريف المُكوِّنات باستخدام الأصناف هنالك نمط شائع لمعالج الأحداث ليكون تابعًا ضمن الصنف، ففي المثال التالي يعرض المُكوِّن Toggle زرًّا يُتيح للمستخدم بأن يقلب بين الحالتين "ON" و "OFF":

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    
	// يكون هذا الربط ضروريًّا لكي تعمل this في رد النداء
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

جرِّب هذا المثال على موقع CodePen.

يجب أن تنتبه إلى معنى this في ردود نداء JSX، ففي JavaScript لا تُربَط توابع الصّنف بشكل افتراضي عن طريق التابع bind()‎، وإن نسيت أن تربط وتُمرِّر this.handleClick إلى onClick، فستكون قيمة this غير مُعرَّفة عند استدعاء الدالة.

لا يُعدُّ هذا سلوكًا مرتبطًا بـ React، بل هو جزء من سياق الدوال في JavaScript. بشكل عام إن أشرت إلى التابع بدون استخدام الأقواس () بعده، مثل ‎onClick={this.handleClick}‎، فيجب أن تربط ذلك التابع.

إن كان استدعاء التابع bind()‎ يزعجك، فهناك طريقتان للالتفاف حول استعماله، إن كنت تستخدم صياغة حقول الصنف العامة التجريبيّة فبإمكانك استخدام حقول الصنف لربط ردود النداء بشكل صحيح:

class LoggingButton extends React.Component {
  // تحرص هذه الصياغة على ربط this ضمن handleClick
  // تحذير: تعتبر هذه صياغة تجريبية experimental
  handleClick = () => {
    console.log('قيمة this هي:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        اضغط هنا
      </button>
    );
  }
}

هذه الصياغة مُمكَّنة بشكل افتراضي عند استخدام الأمر create-react-app. إن لم تكن تستخدم صياغة حقول الأصناف، فبإمكانك استخدام الدوال السّهمية في ردود النداء:

class LoggingButton extends React.Component {
  handleClick() {
    console.log('قيمة this هي:', this);
  }

  render() {
	// تحرص هذه الصياغة على ربط this ضمن handleClick
    return (
      <button onClick={(e) => this.handleClick(e)}>
        اضغط هنا
      </button>
    );
  }
}

المشكلة في هذه الصياغة هي إنشاء رد نداء مختلف في كل مرّة يُصيَّر فيها المُكوِّن LoggingButton، وفي معظم الحالات يكون هذا مقبولًا، ولكن إن مرَّرنا رد النداء هذا كخاصيّة prop إلى المُكوِّنات الموجودة في المستوى الأدنى، فقد تقوم هذه المُكوِّنات بعمل إعادة تصيير (re-rendering) إضافيّة. نوصي بشكل عام الربط في الدالة البانية (constructor) أو استخدام صياغة حقول الأصناف لتجنّب مثل هذا النوع من مشاكل الأداء.

تمرير وسائط إلى معالجات الأحداث

من الشائع أن نحتاج بداخل الحلقات (loops) إلى تمرير مُعامِل إضافي إلى مُعالِج الأحداث، فمثلًا إن كان المتغيّر id يُمثِّل مُعرِّف الصف (row ID)، فسيعمل كلا السطرين التاليين بنفس الكفاءة:

<button onClick={(e) => this.deleteRow(id, e)}>حذف الصف</button>
<button onClick={this.deleteRow.bind(this, id)}>حذف الصف</button>

إنّ السطرين السابقين متكافئان ويستخدمان الدوال السهمية و Function.prototype.bind على التوالي وبالترتيب.

في كلتا الحالتين سيُمرَّر الوسيط e الذي يُمثِّل حدث React كوسيطٍ ثانٍ بعد المُعرِّف ID. في الدوال السهمية يجب أن نُمرِّره بشكلٍ صريح، ولكن في حالة استخدام التابع bind فستُمرَّر أي وسائط أخرى تلقائيًّا.

انظر أيضًا

مصادر