کامپوننت های مرتبه بالاتر در React – بخش ۱

یک کامپوننت مرتبۀ بالاتر (یا HOC) تکنیکی در React برای استفادۀ مجدد منطق کامپوننت است. HOC ها (Higher-Order Components) به خودی خود بخشی از React API نیستند. آنها الگویی هستند که از طبیعت ترکیبی React پدیدار شده‌اند.

به طور مشخص، یک کامپوننت مرتبۀ بالاتر یک تابع است که یک کامپوننت را گرفته و کامپوننتی جدید برمی‌گرداند.

const EnhancedComponent = higherOrderComponent(WrappedComponent);

در حالیکه یک کامپوننت prop ها را در رابط کاربری تبدیل می‌کند، یک کامپوننت مرتبۀ بالاتر یک کامپوننت را در کامپوننت دیگری تبدیل می‌کند.

HOC ها در کتابخانه‌های جانبی React مشترک هستند، مانند connect در Redux و createFragmentContainer در Relay.

در این بخش، می‌خواهیم بررسی کنیم که چرا کامپوننت‌های مرتبۀ بالاتر کارآمد هستند و چگونه کامپوننت مرتبۀ بالاتر خود را بنویسید.

از HOC ها در Cross-Cutting Concerns استفاده کنید

نکته: ما قبلا استفاده از مخلوط‌ها (mixins) را به عنوان راهی برای کنترل نگرانی‌های متقابل توصیه کردیم. ما متوجه شدیم که مخلوط‌ها بیشتر از مقدار فایده‌شان، مشکل ایجاد می‌کنند. اطلاعات بیشتر در مورد اینکه چرا ما استفاده از مخلوط‌ها را کنار گذاشتیم و اینکه چگونه شما می‌توانید کامپوننت‌های موجود خود را تغییر دهید، را می‌توانید ببینید.

کامپوننت‌ها واحدهای اصلی استفادۀ مجدد از کد در React هستند. اگرچه شما خواهید دید که برخی الگوها برای کامپوننت‌های سنتی مناسب نیستند.

برای مثال فرض کنید یک کامپوننت CommentList دارید که بر منبع دیتایی خارجی برای رندر کردن یک لیست از کامنت‌ها صحه می‌گذارد:

class CommentList extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {
      // "DataSource" is some global data source
      comments: DataSource.getComments()
    };
  }

  componentDidMount() {
    // Subscribe to changes
    DataSource.addChangeListener(this.handleChange);
  }

  componentWillUnmount() {
    // Clean up listener
    DataSource.removeChangeListener(this.handleChange);
  }

  handleChange() {
    // Update component state whenever the data source changes
    this.setState({
      comments: DataSource.getComments()
    });
  }

  render() {
    return (
      <div>
        {this.state.comments.map((comment) => (
          <Comment comment={comment} key={comment.id} />
        ))}
      </div>
    );
  }
}

سپس، شما کامپوننتی برای صحه گذاشتن (subscribing) بر یک پست وبلاگ می‌نویسید که الگوی مشابهی را طی می‌کند:

class BlogPost extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {
      blogPost: DataSource.getBlogPost(props.id)
    };
  }

  componentDidMount() {
    DataSource.addChangeListener(this.handleChange);
  }

  componentWillUnmount() {
    DataSource.removeChangeListener(this.handleChange);
  }

  handleChange() {
    this.setState({
      blogPost: DataSource.getBlogPost(this.props.id)
    });
  }

  render() {
    return <TextBlock text={this.state.blogPost} />;
  }
}

CommentList و BlogPost یکسان نیستند، آنها متدهای متفاوتی را در DataSource فراخوانی می‌کنند و خروجی‌های متفاوتی را رندر می‌کنند. ولی خیلی از موارد پیاده‌سازی آنها مشابه یکدیگر است:

  • هنگام نصب (mount) یک تغییر شنونده (change listener) به DataSource اضافه می‌کنند
  • در داخل شنونده هر وقت منبع دیتا تغییر کرد setState را فراخوانی می‌کنند
  • هنگام انحلال(unmount) تغییر شنونده را حذف می‌کنند

می‌توانید تصور کنید که در یک برنامۀ بزرگ، این الگوی صحه گذاشتن (subscribing) DataSource و فراخوانی setState بارها و بارها رخ خواهد داد. ما یک مفهوم نیاز داریم که به ما اجازه دهد این منطق را یک جا تعریف کنیم و در بسیاری از کامپوننت‌ها آنها به اشتراک بگذاریم. یک مزیت کامپوننت مرتبۀ بالاتر است.

ما می‌توانیم تابعی بنویسیم که کامپوننت‌ها را می‌سازد مانند CommentList و BlogPost که بر DataSource صحه می‌گذارند. این تابع یک کامپوننت child را که دیتای subscribe شده را به عنوان prop دریافت کرده، به عنوان آرگومانش می‌پذیرد. بیایید تابع را با withSubscription فراخوانی کنیم:

const CommentListWithSubscription = withSubscription(
  CommentList,
  (DataSource) => DataSource.getComments()
);

const BlogPostWithSubscription = withSubscription(
  BlogPost,
  (DataSource, props) => DataSource.getBlogPost(props.id)
);

اولین پارامتر، کامپوننت بسته بندی شده (wrapped component) است. پارامتر دوم، دیتایی که ما می‌خواهیم، DataSource داده شده و prop های کنونی را بازیابی می‌کند.

وقتی CommentListWithSubscription و BlogPostWithSubscription رندر شدند، CommentList و BlogPost یک data prop با اکثر دیتاهای فعلی بازیابی شده از DataSource را منتقل خواهند کرد:

// This function takes a component...
function withSubscription(WrappedComponent, selectData) {
  // ...and returns another component...
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.handleChange = this.handleChange.bind(this);
      this.state = {
        data: selectData(DataSource, props)
      };
    }

    componentDidMount() {
      // ... that takes care of the subscription...
      DataSource.addChangeListener(this.handleChange);
    }

    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleChange);
    }

    handleChange() {
      this.setState({
        data: selectData(DataSource, this.props)
      });
    }

    render() {
      // ... and renders the wrapped component with the fresh data!
      // Notice that we pass through any additional props
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}

به یاد داشته باشید که یک HOC کامپوننت ورودی را ویرایش نمی‌کند، و آن را به استفاده از وراثتش در کپی رفتارش نیز مجبور نمی‌کند. بلکه یک HOC کامپوننت اصلی را با بسته بندی کردن در کامپوننت container می‌سازد. یک HOC یک تابع خالص بدون هیچ گونه اثرات جانبی است.

و همین. کامپوننت بسته بندی شده، تمام prop های container را در کنار یک prop data جدید که از آن برای رندر خروجی استفاده می‌کند، دریافت می‌کند. HOC کاری با مقدار و چرایی دیتای مورد استفاده ندارد و کامپوننت بسته بندی شده کاری با اینکه دیتا از کجا آمده ندارد.

چون withSubscription یک تابع نرمال است، می‌توانید هر چقدر آرگومان که می‌خواهید اضافه کنید. برای مثال ممکن است شما بخواهید نام data prop را برای جداسازی بیشتر HOC از کامپوننت بسته بندی شده، قابل تنظیم کنید. یا شما می‌توانید یک آرگومان را که shouldComponentUpdate را تنظیم می‌کند و یا آرگومانی که منبع دیتا را تنظیم می‌کند، بپذیرید. این‌ها همه ممکن است زیرا HOC کنترل کاملی روی نحوۀ تعریف کامپوننت دارد.

مانند کامپوننت‌ها، قرارداد بین withSubscription و کامپوننت بسته بندی شده، کاملا بر اساس prop هاست. این موضوع کار جابه‌جایی یک HOC با دیگری را تا زمانی که آنها prop های یکسانی را برای کامپوننت بسته بندی شده فراهم می‌کنند، آسان می‌کند. به عنوان نمونه، این کار ممکن است در تغییر کتابخانه‌های گیرندۀ دیتا کارآمد باشد.

** در مقاله بعدی به ادامه مبحث ( Higher-Order Components ) در React خواهیم پرداخت. مشاهده بخش ۲


مجموعه: مباحث پیشرفتهبرچسب ها
1 Star2 Stars3 Stars4 Stars5 Stars (3 votes, average: 5٫00 out of 5)
Loading...
counter customizable free hit