الفرق بين المراجعتين ل"React/faq functions"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
(تحديث)
 
(7 مراجعات متوسطة بواسطة 3 مستخدمين غير معروضة)
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:تمرير الدوال إلى المكونات}}</noinclude>
+
<noinclude>{{DISPLAYTITLE:تمرير الدوال إلى المكونات في React}}</noinclude>
 
+
== كيف يمكنني تمرير مُعالِج حدث (مثل <code>onClick</code>) إلى مكوّن؟ ==
== كيف يمكنني تمرير مُعالِج أحداث (مثل onClick) إلى المكوّن؟ ==
+
مرِّر مُعالِجات الأحداث والدوال الأخرى كخاصيّات <code>props</code> إلى المكوّنات الأبناء:<syntaxhighlight lang="javascript">
مرِّر مُعالِجات الأحداث والدوال الأخرى كخاصيّات props إلى المكوّنات الأبناء:<syntaxhighlight lang="javascript">
 
 
<button onClick={this.handleClick}>
 
<button onClick={this.handleClick}>
 
</syntaxhighlight>إن احتجت إلى الوصول إلى المكوّن الأب في مُعالِج الأحداث فستحتاج إلى ربط الدالة إلى نسخة المكوّن (مشروحة بالتفصيل في القسم التالي).
 
</syntaxhighlight>إن احتجت إلى الوصول إلى المكوّن الأب في مُعالِج الأحداث فستحتاج إلى ربط الدالة إلى نسخة المكوّن (مشروحة بالتفصيل في القسم التالي).
سطر 40: سطر 39:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== الربط في تابع التصيير render ===
+
=== الربط في تابع التصيير <code>render</code> ===
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
class Foo extends Component {
 
class Foo extends Component {
سطر 51: سطر 50:
 
}
 
}
  
</syntaxhighlight>ملاحظة: يُؤدّي استخدام Function.prototype.bind في التابع render إلى إنشاء دالة جديدة في كل مرّة يُصيَّر فيها المكوّن، ممّا قد يؤثر على الأداء (للمزيد تابع في الأسفل).
+
</syntaxhighlight>'''ملاحظة:''' يُؤدّي استخدام <code>Function.prototype.bind</code> في التابع <code>render</code> إلى إنشاء دالة جديدة في كل مرّة يُصيَّر فيها المكوّن، ممّا قد يؤثر على الأداء (للمزيد تابع في الأسفل).
  
=== استخدام الدوال السهميّة في تابع التصيير render ===
+
=== استخدام الدوال السهميّة في تابع التصيير <code>render</code> ===
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
class Foo extends Component {
 
class Foo extends Component {
سطر 64: سطر 63:
 
}
 
}
  
</syntaxhighlight>ملاحظة: يُؤدّي استخدام الدوال السهميّة في التابع render إلى إنشاء دالة جديدة في كل مرّة يُصيَّر فيها المكوّن، ممّا قد يؤثر على الأداء (للمزيد تابع في الأسفل).
+
</syntaxhighlight>'''ملاحظة:''' يُؤدّي استخدام الدوال السهميّة في التابع <code>render</code> إلى إنشاء دالة جديدة في كل مرّة يُصيَّر فيها المكوّن، ممّا قد يؤثر على الأداء (للمزيد تابع في الأسفل).
  
 
== هل من الجيّد استخدام الدوال السهميّة في توابع التصيير؟ ==
 
== هل من الجيّد استخدام الدوال السهميّة في توابع التصيير؟ ==
 
بشكلٍ عام نعم، لا مشكلة في ذلك، وهي عادةً الطريقة الأسهل لتمرير المُعامِلات إلى دوال ردود النداء.
 
بشكلٍ عام نعم، لا مشكلة في ذلك، وهي عادةً الطريقة الأسهل لتمرير المُعامِلات إلى دوال ردود النداء.
  
إن كانت لديك مشاكل في الأداء، فحاول تحسينه عن طريق الطرق المشروحة في توثيق React.
+
إن كانت لديك مشاكل في الأداء، فحاول تحسينه عبر الطرق المشروحة في توثيق React.
  
 
== لماذا من الضروري إجراء الربط أساسًا؟ ==
 
== لماذا من الضروري إجراء الربط أساسًا؟ ==
في JavaScript لا تكون الشيفرتان التاليتان متساويتين:
+
في JavaScript لا تكون الشيفرتان التاليتان متساويتين:<syntaxhighlight lang="javascript">
 +
obj.method();
 +
 
 +
</syntaxhighlight><syntaxhighlight lang="javascript">
 +
var method = obj.method;
 +
method();
 +
 
 +
</syntaxhighlight>تضمن توابع الربط عمل الشيفرة الثانية بنفس طريقة عمل الشيفرة الأولى.
 +
 
 +
تحتاج باستخدام React فقط إلى ربط التوابع التي تُمرِّرها إلى المكوّنات الأخرى. على سبيل المثال يُمرِّر <code>‎<button onClick={this.handleClick}>‎</code> التابع <code>this.handleClick</code> لذا تحتاج إلى ربطه، ولكن من غير الضروري ربط التابع <code>render</code> أو توابع دورة حياة المكوّن، فنحن لا نُمرّرها إلى المكوّنات الأخرى.
 +
 
 +
يشرح [http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/ هذا المنشور] مفهوم الربط (binding) وكيفيّة عمل الدوال في JavaScript بالتفصيل.
 +
 
 +
== لماذا تُستدعى الدالة لدي في كل مرّة يُصيَّر فيها المكوّن؟ ==
 +
تأكّد من عدم استدعاء الدالة عند تمريرها إلى المكوّن:<syntaxhighlight lang="javascript">
 +
render() {
 +
  // خطأ: يُستدعى handleClick بدلًا من تمريره كمرجع
 +
  return <button onClick={this.handleClick()}>انقر هنا</button>
 +
}
 +
 
 +
</syntaxhighlight>بدلًا من فعل ذلك مرِّر الدالة نفسها بدون أقواس:<syntaxhighlight lang="javascript">
 +
render() {
 +
  // صحيح: تُمرّر الدالة handleClick كمرجع هنا
 +
  return <button onClick={this.handleClick}>انقر هنا</button>
 +
}
 +
 
 +
</syntaxhighlight>
 +
 
 +
== كيف أمرّر مُعامِل إلى مُعالِج الأحداث أو رد النداء؟ ==
 +
تستطيع استخدام الدوال السهميّة من أجل الالتفاف حول مُعالِجات الأحداث وتمرير المُعامِلات:<syntaxhighlight lang="javascript">
 +
<button onClick={() => this.handleClick(id)} />
 +
 
 +
</syntaxhighlight>يُكافِئ هذا استدعاء ‎<code>.bind</code>:<syntaxhighlight lang="javascript">
 +
<button onClick={this.handleClick.bind(this, id)} />
 +
 
 +
</syntaxhighlight>
 +
 
 +
=== مثال: تمرير المُعامِلات باستخدام الدوال السهميّة ===
 +
<syntaxhighlight lang="javascript">
 +
const A = 65 // ASCII حرف
 +
 
 +
class Alphabet extends React.Component {
 +
  constructor(props) {
 +
    super(props);
 +
    this.state = {
 +
      justClicked: null,
 +
      letters: Array.from({length: 26}, (_, i) => String.fromCharCode(A + i))
 +
    };
 +
  }
 +
  handleClick(letter) {
 +
    this.setState({ justClicked: letter });
 +
  }
 +
  render() {
 +
    return (
 +
      <div>
 +
        Just clicked: {this.state.justClicked}
 +
        <ul>
 +
          {this.state.letters.map(letter =>
 +
            <li key={letter} onClick={() => this.handleClick(letter)}>
 +
              {letter}
 +
            </li>
 +
          )}
 +
        </ul>
 +
      </div>
 +
    )
 +
  }
 +
}
 +
 
 +
</syntaxhighlight>
 +
 
 +
=== مثال: تمرير المُعامِلات باستخدام خاصيّات البيانات ===
 +
بإمكانك عوض ذلك استخدام واجهات برمجة التطبيق في DOM لتزين البيانات التي تحتاجها من أجل مُعالِجات الأحداث. اتبع هذه الطريقة إن احتجت لضبط عدد كبير من العناصر أو كنتَ تمتلك شجرة تصيير تعتمد على اختبارات التساوي الخاصّة بالصنف <code>React.PureComponent</code>:<syntaxhighlight lang="javascript">
 +
const A = 65 // ASCII حرف
 +
 
 +
class Alphabet extends React.Component {
 +
  constructor(props) {
 +
    super(props);
 +
    this.handleClick = this.handleClick.bind(this);
 +
    this.state = {
 +
      justClicked: null,
 +
      letters: Array.from({length: 26}, (_, i) => String.fromCharCode(A + i))
 +
    };
 +
  }
 +
 
 +
  handleClick(e) {
 +
    this.setState({
 +
      justClicked: e.target.dataset.letter
 +
    });
 +
  }
 +
 
 +
  render() {
 +
    return (
 +
      <div>
 +
        Just clicked: {this.state.justClicked}
 +
        <ul>
 +
          {this.state.letters.map(letter =>
 +
            <li key={letter} data-letter={letter} onClick={this.handleClick}>
 +
              {letter}
 +
            </li>
 +
          )}
 +
        </ul>
 +
      </div>
 +
    )
 +
  }
 +
}
 +
 
 +
</syntaxhighlight>
 +
 
 +
== كيف أستطيع منع استدعاء الدالة بسرعة كبيرة أو مرات عديدة؟ ==
 +
إن كان لديك مُعالِج أحداث مثل <code>onClick</code> أو <code>onScroll</code> وكنتَ ترغب في منع إطلاق رد النداء بسرعة كبيرة، فتستطيع تحديد معدّل تنفيذ رد النداء. يُمكِن فعل ذلك باستخدام:
 +
* تقنية الخنق (throttling): معاينة التغييرات بناءً على تردد معتمد على الوقت (باستخدام <code>[https://lodash.com/docs#throttle ‎_.throttle]</code>).
 +
* منع الارتداد (debouncing): نشر التغييرات بعد مدّة زمنيّة معينة من عدم الفاعليّة (باستخدام ‎<code>[https://lodash.com/docs#debounce _.debounce]</code>).
 +
* الخنق باستخدام <code>requestAnimationFrame</code> : معاينة التغييرات بناءً على <code>[https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame requestAnimationFrame]</code> (باستخدام <code>[https://github.com/alexreardon/raf-schd raf-schd]</code>).
 +
انظر إلى [http://demo.nimius.net/debounce_throttle/ هذا المخطط] للمقارنة بين الدالتين <code>throttle</code> و <code>debounce</code>.
 +
 
 +
'''ملاحظة:''' تُزوّدنا الدوال ‎<code>_.debounce</code>، و ‎<code>_.throttle</code>، و <code>raf-schd</code> بتابع للإلغاء <code>cancel</code> لإلغاء ردود النداء المتأخرة. يجب إمّا استدعاء هذا التابع من خلال التابع <code>componentWillUnmount</code> أو التحقق من أنّ المكون لا يزال موصولًا ضمن دالة التأخير.
 +
 
 +
=== الخنق (Throttle) ===
 +
يمنع الخنق استدعاء الدالة أكثر من مرّة ضمن النافذة الزمنيّة المُعطاة. يخنق المثال التالي مُعالِج الأحداث <code>click</code> لمنع استدعائه أكثر من مرّة في الثانية:<syntaxhighlight lang="javascript">
 +
import throttle from 'lodash.throttle';
 +
 
 +
class LoadMoreButton extends React.Component {
 +
  constructor(props) {
 +
    super(props);
 +
    this.handleClick = this.handleClick.bind(this);
 +
    this.handleClickThrottled = throttle(this.handleClick, 1000);
 +
  }
 +
 
 +
  componentWillUnmount() {
 +
    this.handleClickThrottled.cancel();
 +
  }
 +
 
 +
  render() {
 +
    return <button onClick={this.handleClickThrottled}>تحميل المزيد</button>;
 +
  }
 +
 
 +
  handleClick() {
 +
    this.props.loadMore();
 +
  }
 +
}
 +
 
 +
</syntaxhighlight>
 +
 
 +
=== منع الارتداد (Debounce) ===
 +
يضمن منع الارتداد عدم تنفيذ الدالة حتى مرور فترة معينة من الوقت منذ آخر استدعاء لها. يكون هذا مفيدًا عندما يتوجب عليك إجراء بعض الحسابات المكلفة استجابةً لحدث قد ينتهي بسرعة (مثل النزول بالصفحة scroll أو أحداث لوحة المفاتيح). يمنع المثال التالي الارتداد في حقل إدخال نصي مع تأخير 250 ميلي ثانية:<syntaxhighlight lang="javascript">
 +
import debounce from 'lodash.debounce';
 +
 
 +
class Searchbox extends React.Component {
 +
  constructor(props) {
 +
    super(props);
 +
    this.handleChange = this.handleChange.bind(this);
 +
    this.emitChangeDebounced = debounce(this.emitChange, 250);
 +
  }
 +
 
 +
  componentWillUnmount() {
 +
    this.emitChangeDebounced.cancel();
 +
  }
 +
 
 +
  render() {
 +
    return (
 +
      <input
 +
        type="text"
 +
        onChange={this.handleChange}
 +
        placeholder="Search..."
 +
        defaultValue={this.props.value}
 +
      />
 +
    );
 +
  }
 +
 
 +
  handleChange(e) {
 +
    this.emitChangeDebounced(e.target.value);
 +
  }
 +
 
 +
  emitChange(value) {
 +
    this.props.onChange(value);
 +
  }
 +
}
 +
</syntaxhighlight>
 +
 
 +
=== الخنق باستخدام <code>requestAnimationFrame</code> ===
 +
إنّ <code>[https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame requestAnimationFrame]</code> هو عبارة عن طريقة لوضع الدالة في طابور لتنفيذه في المتصفح في الوقت المثالي لتحسين أداء التصيير. تُطلَق الدالة التي توضع في الطابور باستخدام <code>requestAnimationFrame</code> في الإطار الزمني التالي. سيعمل المتصفح بجد لضمان الحصول على 60 إطار في الثانية (60 fps). إن لم يكن المتصفح قادرًا على ذلك فسيحدد عدد الإطارات في الثانية. على سبيل المثال قد يكون جهازك قادر على التعامل فقط مع 30 إطار بالثانية لذا ستحصل على 30 إطار بالثانية فقط. إنّ استخدام <code>requestAnimationFrame</code> للخنق هو تقنية مفيدة تمنع من إجراء أكثر من 60 تحديث في الثانية. إن كنت تجري 100 تحديث في الثانية فسيؤدي ذلك إلى إنشاء عمل إضافي على المتصفح والذي لن يراه المستخدم على أية حال.
 +
 
 +
'''ملاحظة:''' استخدام هذه التقنية سيلتقط فقط آخر قيمة منشورة في الإطار. بإمكانك رؤية مثال حول كيفية عمل هذا الضبط من [https://developer.mozilla.org/en-US/docs/Web/Events/scroll هنا].<syntaxhighlight lang="javascript">
 +
import rafSchedule from 'raf-schd';
 +
 
 +
class ScrollListener extends React.Component {
 +
  constructor(props) {
 +
    super(props);
 +
 
 +
    this.handleScroll = this.handleScroll.bind(this);
 +
 
 +
// إنشاء دالة جديدة لجدولة التحديثات
 +
    this.scheduleUpdate = rafSchedule(
 +
      point => this.props.onScroll(point)
 +
    );
 +
  }
 +
 
 +
  handleScroll(e) {
 +
// عند استقبال حدث scroll جدول تحديثًا
 +
// إن استقبلنا الكثير من التحديثات ضمن الإطار فسننشر آخر قيمة فقط
 +
    this.scheduleUpdate({ x: e.clientX, y: e.clientY });
 +
  }
 +
 
 +
  componentWillUnmount() {
 +
// إلغاء أي تحديثات منتظرة بما أننا سنفصل المكون
 +
    this.scheduleUpdate.cancel();
 +
  }
 +
 
 +
  render() {
 +
    return (
 +
      <div
 +
        style={{ overflow: 'scroll' }}
 +
        onScroll={this.handleScroll}
 +
      >
 +
        <img src="/my-huge-image.jpg" />
 +
      </div>
 +
    );
 +
  }
 +
}
 +
 
 +
</syntaxhighlight>
 +
 
 +
=== اختبار حدود معدل تحديث الإطار لديك ===
 +
عند اختبار حدود معدل تحديث الإطار من المفيد امتلاك القدرة على تمرير الزمن بسرعة. إن كنت تستخدم [https://facebook.github.io/jest/ jest] بإمكانك استخدام [https://facebook.github.io/jest/docs/en/timer-mocks.html محاكيات الوقت] لتمرير الوقت بسرعة. إن كنت تستخدم الخنق عن طريق <code>requestAnimationFrame</code> فهنالك الأداة <code>[https://github.com/alexreardon/raf-stub raf-stub]</code> مفيدة للتحكم بضبط تحريك الإطارات.
 +
== انظر أيضًا ==
 +
* [[React/glossary|المصطلحات]]
 +
* [[React/faq ajax|استخدام AJAX مع React]]
 +
* [[React/faq build|أسئلة حول Babel، و JSX، وخطوات بناء التطبيقات]]
 +
* [[React/faq state|حالة المكونات]]
 +
* [[React/faq styling|التنسيق واستخدام CSS مع React]]
 +
* [[React/faq structure|بنية ملفات المشروع]]
 +
* [[React/faq versioning|سياسة الإصدارات المتبعة في React]]
 +
* [[React/faq internals|DOM الافتراضي والكائنات الداخلية]]
 +
==مصادر==
 +
*[https://reactjs.org/docs/faq-functions.html صفحة تمرير الدوال إلى المكونات في توثيق React الرسمي].
 +
[[تصنيف:React]]
 +
[[تصنيف:React FAQ]]

المراجعة الحالية بتاريخ 09:42، 7 نوفمبر 2020

كيف يمكنني تمرير مُعالِج حدث (مثل onClick) إلى مكوّن؟

مرِّر مُعالِجات الأحداث والدوال الأخرى كخاصيّات props إلى المكوّنات الأبناء:

<button onClick={this.handleClick}>

إن احتجت إلى الوصول إلى المكوّن الأب في مُعالِج الأحداث فستحتاج إلى ربط الدالة إلى نسخة المكوّن (مشروحة بالتفصيل في القسم التالي).

كيف أربط الدالة إلى نسخة المكوّن؟

هنالك عدة طرق للتأكّد من أنّ الدوال تستطيع الوصول إلى خاصيّات المكوّن مثل this.props و this.state، بناءً على الصياغة وخطوات البناء التي تستخدمها.

الربط في الدالة البانية (ES2015)

class Foo extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log('حدثت نقرة');
  }
  render() {
    return <button onClick={this.handleClick}>انقر هنا</button>;
  }
}

خاصيّات الصنف (اقتراح المرحلة 3)

class Foo extends Component {
  // ملاحظة: هذه الصياغة تجريبية وليست معيارية بعد
  handleClick = () => {
    console.log('حدثت نقرة');
  }
  render() {
    return <button onClick={this.handleClick}>انقر هنا</button>;
  }
}

الربط في تابع التصيير render

class Foo extends Component {
  handleClick() {
    console.log('حدثت نقرة');
  }
  render() {
    return <button onClick={this.handleClick.bind(this)}>انقر هنا</button>;
  }
}

ملاحظة: يُؤدّي استخدام Function.prototype.bind في التابع render إلى إنشاء دالة جديدة في كل مرّة يُصيَّر فيها المكوّن، ممّا قد يؤثر على الأداء (للمزيد تابع في الأسفل).

استخدام الدوال السهميّة في تابع التصيير render

class Foo extends Component {
  handleClick() {
    console.log('حدثت نقرة');
  }
  render() {
    return <button onClick={() => this.handleClick()}>انقر هنا</button>;
  }
}

ملاحظة: يُؤدّي استخدام الدوال السهميّة في التابع render إلى إنشاء دالة جديدة في كل مرّة يُصيَّر فيها المكوّن، ممّا قد يؤثر على الأداء (للمزيد تابع في الأسفل).

هل من الجيّد استخدام الدوال السهميّة في توابع التصيير؟

بشكلٍ عام نعم، لا مشكلة في ذلك، وهي عادةً الطريقة الأسهل لتمرير المُعامِلات إلى دوال ردود النداء.

إن كانت لديك مشاكل في الأداء، فحاول تحسينه عبر الطرق المشروحة في توثيق React.

لماذا من الضروري إجراء الربط أساسًا؟

في JavaScript لا تكون الشيفرتان التاليتان متساويتين:

obj.method();
var method = obj.method;
method();

تضمن توابع الربط عمل الشيفرة الثانية بنفس طريقة عمل الشيفرة الأولى.

تحتاج باستخدام React فقط إلى ربط التوابع التي تُمرِّرها إلى المكوّنات الأخرى. على سبيل المثال يُمرِّر ‎<button onClick={this.handleClick}>‎ التابع this.handleClick لذا تحتاج إلى ربطه، ولكن من غير الضروري ربط التابع render أو توابع دورة حياة المكوّن، فنحن لا نُمرّرها إلى المكوّنات الأخرى.

يشرح هذا المنشور مفهوم الربط (binding) وكيفيّة عمل الدوال في JavaScript بالتفصيل.

لماذا تُستدعى الدالة لدي في كل مرّة يُصيَّر فيها المكوّن؟

تأكّد من عدم استدعاء الدالة عند تمريرها إلى المكوّن:

render() {
  // خطأ: يُستدعى handleClick بدلًا من تمريره كمرجع
  return <button onClick={this.handleClick()}>انقر هنا</button>
}

بدلًا من فعل ذلك مرِّر الدالة نفسها بدون أقواس:

render() {
  // صحيح: تُمرّر الدالة handleClick كمرجع هنا
  return <button onClick={this.handleClick}>انقر هنا</button>
}

كيف أمرّر مُعامِل إلى مُعالِج الأحداث أو رد النداء؟

تستطيع استخدام الدوال السهميّة من أجل الالتفاف حول مُعالِجات الأحداث وتمرير المُعامِلات:

<button onClick={() => this.handleClick(id)} />

يُكافِئ هذا استدعاء ‎.bind:

<button onClick={this.handleClick.bind(this, id)} />

مثال: تمرير المُعامِلات باستخدام الدوال السهميّة

const A = 65 // ASCII حرف

class Alphabet extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      justClicked: null,
      letters: Array.from({length: 26}, (_, i) => String.fromCharCode(A + i))
    };
  }
  handleClick(letter) {
    this.setState({ justClicked: letter });
  }
  render() {
    return (
      <div>
        Just clicked: {this.state.justClicked}
        <ul>
          {this.state.letters.map(letter =>
            <li key={letter} onClick={() => this.handleClick(letter)}>
              {letter}
            </li>
          )}
        </ul>
      </div>
    )
  }
}

مثال: تمرير المُعامِلات باستخدام خاصيّات البيانات

بإمكانك عوض ذلك استخدام واجهات برمجة التطبيق في DOM لتزين البيانات التي تحتاجها من أجل مُعالِجات الأحداث. اتبع هذه الطريقة إن احتجت لضبط عدد كبير من العناصر أو كنتَ تمتلك شجرة تصيير تعتمد على اختبارات التساوي الخاصّة بالصنف React.PureComponent:

const A = 65 // ASCII حرف

class Alphabet extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
    this.state = {
      justClicked: null,
      letters: Array.from({length: 26}, (_, i) => String.fromCharCode(A + i))
    };
  }

  handleClick(e) {
    this.setState({
      justClicked: e.target.dataset.letter
    });
  }

  render() {
    return (
      <div>
        Just clicked: {this.state.justClicked}
        <ul>
          {this.state.letters.map(letter =>
            <li key={letter} data-letter={letter} onClick={this.handleClick}>
              {letter}
            </li>
          )}
        </ul>
      </div>
    )
  }
}

كيف أستطيع منع استدعاء الدالة بسرعة كبيرة أو مرات عديدة؟

إن كان لديك مُعالِج أحداث مثل onClick أو onScroll وكنتَ ترغب في منع إطلاق رد النداء بسرعة كبيرة، فتستطيع تحديد معدّل تنفيذ رد النداء. يُمكِن فعل ذلك باستخدام:

  • تقنية الخنق (throttling): معاينة التغييرات بناءً على تردد معتمد على الوقت (باستخدام ‎_.throttle).
  • منع الارتداد (debouncing): نشر التغييرات بعد مدّة زمنيّة معينة من عدم الفاعليّة (باستخدام ‎_.debounce).
  • الخنق باستخدام requestAnimationFrame : معاينة التغييرات بناءً على requestAnimationFrame (باستخدام raf-schd).

انظر إلى هذا المخطط للمقارنة بين الدالتين throttle و debounce.

ملاحظة: تُزوّدنا الدوال ‎_.debounce، و ‎_.throttle، و raf-schd بتابع للإلغاء cancel لإلغاء ردود النداء المتأخرة. يجب إمّا استدعاء هذا التابع من خلال التابع componentWillUnmount أو التحقق من أنّ المكون لا يزال موصولًا ضمن دالة التأخير.

الخنق (Throttle)

يمنع الخنق استدعاء الدالة أكثر من مرّة ضمن النافذة الزمنيّة المُعطاة. يخنق المثال التالي مُعالِج الأحداث click لمنع استدعائه أكثر من مرّة في الثانية:

import throttle from 'lodash.throttle';

class LoadMoreButton extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
    this.handleClickThrottled = throttle(this.handleClick, 1000);
  }

  componentWillUnmount() {
    this.handleClickThrottled.cancel();
  }

  render() {
    return <button onClick={this.handleClickThrottled}>تحميل المزيد</button>;
  }

  handleClick() {
    this.props.loadMore();
  }
}

منع الارتداد (Debounce)

يضمن منع الارتداد عدم تنفيذ الدالة حتى مرور فترة معينة من الوقت منذ آخر استدعاء لها. يكون هذا مفيدًا عندما يتوجب عليك إجراء بعض الحسابات المكلفة استجابةً لحدث قد ينتهي بسرعة (مثل النزول بالصفحة scroll أو أحداث لوحة المفاتيح). يمنع المثال التالي الارتداد في حقل إدخال نصي مع تأخير 250 ميلي ثانية:

import debounce from 'lodash.debounce';

class Searchbox extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.emitChangeDebounced = debounce(this.emitChange, 250);
  }

  componentWillUnmount() {
    this.emitChangeDebounced.cancel();
  }

  render() {
    return (
      <input
        type="text"
        onChange={this.handleChange}
        placeholder="Search..."
        defaultValue={this.props.value}
      />
    );
  }

  handleChange(e) {
    this.emitChangeDebounced(e.target.value);
  }

  emitChange(value) {
    this.props.onChange(value);
  }
}

الخنق باستخدام requestAnimationFrame

إنّ requestAnimationFrame هو عبارة عن طريقة لوضع الدالة في طابور لتنفيذه في المتصفح في الوقت المثالي لتحسين أداء التصيير. تُطلَق الدالة التي توضع في الطابور باستخدام requestAnimationFrame في الإطار الزمني التالي. سيعمل المتصفح بجد لضمان الحصول على 60 إطار في الثانية (60 fps). إن لم يكن المتصفح قادرًا على ذلك فسيحدد عدد الإطارات في الثانية. على سبيل المثال قد يكون جهازك قادر على التعامل فقط مع 30 إطار بالثانية لذا ستحصل على 30 إطار بالثانية فقط. إنّ استخدام requestAnimationFrame للخنق هو تقنية مفيدة تمنع من إجراء أكثر من 60 تحديث في الثانية. إن كنت تجري 100 تحديث في الثانية فسيؤدي ذلك إلى إنشاء عمل إضافي على المتصفح والذي لن يراه المستخدم على أية حال.

ملاحظة: استخدام هذه التقنية سيلتقط فقط آخر قيمة منشورة في الإطار. بإمكانك رؤية مثال حول كيفية عمل هذا الضبط من هنا.

import rafSchedule from 'raf-schd';

class ScrollListener extends React.Component {
  constructor(props) {
    super(props);

    this.handleScroll = this.handleScroll.bind(this);

	// إنشاء دالة جديدة لجدولة التحديثات
    this.scheduleUpdate = rafSchedule(
      point => this.props.onScroll(point)
    );
  }

  handleScroll(e) {
	// عند استقبال حدث scroll جدول تحديثًا
	// إن استقبلنا الكثير من التحديثات ضمن الإطار فسننشر آخر قيمة فقط
    this.scheduleUpdate({ x: e.clientX, y: e.clientY });
  }

  componentWillUnmount() {
	// إلغاء أي تحديثات منتظرة بما أننا سنفصل المكون
    this.scheduleUpdate.cancel();
  }

  render() {
    return (
      <div
        style={{ overflow: 'scroll' }}
        onScroll={this.handleScroll}
      >
        <img src="/my-huge-image.jpg" />
      </div>
    );
  }
}

اختبار حدود معدل تحديث الإطار لديك

عند اختبار حدود معدل تحديث الإطار من المفيد امتلاك القدرة على تمرير الزمن بسرعة. إن كنت تستخدم jest بإمكانك استخدام محاكيات الوقت لتمرير الوقت بسرعة. إن كنت تستخدم الخنق عن طريق requestAnimationFrame فهنالك الأداة raf-stub مفيدة للتحكم بضبط تحريك الإطارات.

انظر أيضًا

مصادر