انتقال توابع به کامپوننت ها در React

چگونه یک کنترل کنندۀ رویداد (مانند onClick) را به یک کامپوننت منتقل کنم؟

کنترل کننده‌های رویداد و توابع دیگر را به عنوان props به کامپوننت های child منتقل کنید:

<button onClick={this.handleClick}>

اگر نیاز دارید در یک کنترل کننده به کامپوننت والد دسترسی داشته باشید، نیاز خواهید داشت تابع را هم در شیء کامپوننت bind کنید.

چگونه یک تابع را در شیء کامپوننت bind کنم؟

بر اساس این که از چه سینتکس و گام ساختی استفاده می‌کنید، راه‌های متفاوتی برای مطمئن شدن از این که توابع به ویژگی‌های کامپوننت مانند this.props و this.state دسترسی دارند وجود دارد.

Bind کردن در Constructor (ES2015)

class Foo extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <button onClick={this.handleClick}>Click Me</button>;
  }
}

Class Properties

class Foo extends Component {
  // Note: this syntax is experimental and not standardized yet.
  handleClick = () => {
    console.log('Click happened');
  }
  render() {
    return <button onClick={this.handleClick}>Click Me</button>;
  }
}

Bind کردن در رندر

class Foo extends Component {
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <button onClick={this.handleClick.bind(this)}>Click Me</button>;
  }
}

نکته: استفاده از Function.prototype.bind در رندر، یک تابع جدید را در هر مرتبه‌ای که کامپوننت رندر می‌کند می‌سازد که ممکن است پیامدهایی در عملکرد داشته باشد.

تابع پیکانی در رندر

class Foo extends Component {
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <button onClick={() => this.handleClick()}>Click Me</button>;
  }
}

نکته: استفاده از تابع پیکانی در رندر، یک تابع جدید را در هر مرتبه‌ای که کامپوننت رندر می‌کند می‌سازد که ممکن است پیامدهایی در عملکرد داشته باشد.

استفاده از توابع پیکانی در متدهای رندر مشکلی ندارد؟

به طور کلی بله مشکلی ندارد و حتی در اغلب موارد آسان‌ترین راه برای انتقال پارامترها به توابع فراخوانی است.

اگر مشکلات عملکردی دارید، به هر حال، بهینه سازی کنید.

به طور کلی چرا bind کردن لازم است؟

در جاوا اسکریپت، این دو قطعه کد با هم برابر نیستند:

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

متدهای bind کردن کمک می‌کنند تا مطمئن شویم کد دوم مشابه کد اول عمل می‌کند.

با React، به طور معمول شما فقط نیاز به bind کردن متدهایی که به کامپوننت های دیگر منتقل کردید دارید. برای مثال، <button onClick={this.handleClick}> کار انتقال this.handleClick را انجام می‌دهد لذا می‌خواهید آن را bind کنید. هرچند نیازی به bind کردن متد رندر یا متدهای چرخۀ زندگی نیست: ما آنها را به کامپوننت های دیگر منتقل نمی‌کنیم.

پستی از Yehuda Katz با جزئیات نشان می‌دهد که bind کردن چیست و توابع چگونه در جاوا اسکریپت کار می‌کنند.

چرا هر مرتبه که کامپوننت رندر می‌کند، تابع من فراخوانی می‌شود؟

مطمئن شوید که شما هنگام انتقال تابع به کامپوننت، آن را فرخوانی نمی‌کنید:

render() {
  // Wrong: handleClick is called instead of passed as a reference!
  return <button onClick={this.handleClick()}>Click Me</button>
}

در مقابل، خود تابع را بدون پرانتزها منتقل کنید:

render() {
  // Correct: handleClick is passed as a reference!
  return <button onClick={this.handleClick}>Click Me</button>
}

چگونه باید یک پارامتر را به یک کنترل کنندۀ رویداد یا یک بازخوانی منتقل کنم؟

شما می‌توانید از یک تابع پیکانی جهت بسته بندی یک کنترل کنندۀ رویداد و انتقال پارامترها استفاده کنید:

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

این معادل با فراخوانی .bind است:

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

مثال: انتقال پارامترها با استفاده از توابع پیکانی

const A = 65 // ASCII character code

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(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>
    )
  }
}

مثال: انتقال پارامترها با استفاده از data-attributes

به عنوان روشی دیگر، شما می‌توانید از API های DOM برای ذخیرۀ دیتای مورد نیاز کنترل کننده‌های رویدادها استفاده کنید. این روش را هنگامی که نیاز به بهینه سازی تعداد زیادی المان داشتید و یا یک درخت رندر داشتید که بر کنترل برابری React.PureComponent تکیه کرده است، در نظر داشته باشید.

const A = 65 // ASCII character code

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 دارید و می‌خواهید از فراخوانی خیلی زود آن جلوگیری کنید، می‌توانید سرعت اجرای فراخوانی را محدود کنید. این کار می‌تواند با استفاده از اینها انجام شود:

  • مهار کردن (Throttle): تغییرات نمونه بر اساس فرکانس زمانی (مانند _.throttle)
  • Debouncing: تغییرات انتشار پس از دوره‌ای از عدم فعالیت (مانند _.debounce)
  • مهار کردن requestAnimationFrame: تغییرات نمونه بر اساس requestAnimationFrame (مانند raf-schd)

می‌توانید این نمونه تصویری را برای مقایسۀ توابع throttle و debounce ببینید.

نکته: _.debounce و , _.throttle و raf-schd یک متد cancel را برای کنسل کردن فراخوانی‌های به تاخیر افتاده مهیا می‌کند. شما یا باید این متد را از componentWillUnmount فراخوانی کنید یا مطمئن شوید که کامپوننت همچنان داخل تابع به تاخیر افتاده mount شده است.

Throttle

مهار کردن (Throttling) از فراخوانی یک تابع بیش از یک بار در یک پنجرۀ زمانی داده شده جلوگیری می‌کند. مثال زیر یک کنترل کنندۀ کلیک را مهار می‌کند تا از فراخوانی بیش از یک بار آن در ثانیه جلوگیری کند.

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}>Load More</button>;
  }

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

Debounce

با Debounce کردن مطمئن خواهیم شد که یک تابع اجرا نخواهد شد تا اینکه مدت زمان معینی از آخرین فراخوانی آن بگذرد. این کار هنگامی مفید است که نیاز داشته باشید محاسبات سنگینی را در پاسخ به یک رویداد که ممکن است به سرعت روانه شود اجرا کنید (مانند رویدادهای کیبرد). مثال زیر متن ورودی را با ۲۵۰ میلی ثانیه Debounce می‌کند.

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) {
    // React pools events, so we read the value before debounce.
    // Alternately we could call `event.persist()` and pass the entire event.
    // For more info see reactjs.org/docs/events.html#event-pooling
    this.emitChangeDebounced(e.target.value);
  }

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

مهار requestAnimationFrame

requestAnimationFrame راهی برای به صف در آوردن یک تابع جهت اجرا در مرورگر در زمان بهینۀ عملکرد رندر است. تابعی که با requestAnimationFrame به صف کشیده شده است در فریم بعدی اجرا خواهد شد. مرورگر سخت در تلاش است تا مطمئن شود ۶۰ فریم در هر ثانیه خواهد بود. هرچند اگر مرورگر نتواند آن را انجام دهد، به طور طبیعی مقدار فریم‌ها در هر ثانیه را محدود می‌کند. برای مثال ممکن است یک دستگاه تنها بتواند ۳۰ فریم در هر ثانیه را کنترل کند بنابراین شما در هر ثانیه ۳۰ فریم خواهید داشت. استفاده از مهار requestAnimationFrame تکنیکی مناسب جهت جلوگیری از انجام بیش از ۶۰ آپدیت در هر ثانیه است. اگر در حال انجام ۱۰۰ آپدیت در هر ثانیه هستید، یک کار اضافی برای مرورگر خواهد بود که در هر حال کاربر آن را نخواهد دید.

نکته: استفاده از این تکنیک فقط آخرین مقدار منتشر شده در یک فریم را به دست می‌دهد. شما می‌توانید مثالی از نحوۀ کار کردن این بهینه سازی را درMDN ببینید.

import rafSchedule from 'raf-schd';

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

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

    // Create a new function to schedule updates.
    this.scheduleUpdate = rafSchedule(
      point => this.props.onScroll(point)
    );
  }

  handleScroll(e) {
    // When we receive a scroll event, schedule an update.
    // If we receive many updates within a frame, we'll only publish the latest value.
    this.scheduleUpdate({ x: e.clientX, y: e.clientY });
  }

  componentWillUnmount() {
    // Cancel any pending updates since we're unmounting.
    this.scheduleUpdate.cancel();
  }

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

تست محدودیت سرعت شما

وقتی تست محدودیت سرعت شما به درستی کار کند، به شما توانایی سرعت بخشیدن حرکت به جلو را می‌دهد. اگر در حال استفاده از jest هستید، می‌توانید از mock timers جهت تسریع حرکت به جلو استفاده کنید. اگر در حال استفاده از requestAnimationFrame هستید، احتمالا raf-stub ابزار مناسبی برای کنترل فریم‌های انیمیشن خواهد بود.

1 Star2 Stars3 Stars4 Stars5 Stars (3 votes, average: 4٫67 out of 5)
Loading...

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

counter customizable free hit