Hôm nay tôi sẽ chia sẻ với các bạn cách để giải quyết vấn đề calback hell thỉnh thoảng xảy ra trong các component React, đó là sử dụng react-delegate. Chúng tôi sử dụng nó gần như mọi lúc trong ứng dụng.

Khi bạn bắt đầu sử dụng React, không sớm thì muộn bạn cũng sẽ gặp phải trường hợp callback hell trong component như dưới đây:

<Task
  task={task}
  onTaskComplete={this.onTaskComplete}
  onTaskDelete={this.onTaskDelete}
  onTaskSchedule={this.onTaskSchedule}
/>

khi các component con cần làm khá nhiều việc để giao tiếp với các component cha thông qua callback. Tôi gọi vấn đề này là over-parenting :3. Bạn sẽ cần các component con tự quản lý state của nó.

Vấn đề sẽ càng trở nên phức tạp hơn khi component cần giao tiếp với các component cha của component cha của nó (tạm gọi là component ông :>)

<Subtask
  subtask={task.subtask}
  onSubtaskComplete={this.props.onTaskComplete}
  onSubtaskDelete={this.props.onTaskDelete}
  onSubtaskSchedule={this.props.onTaskSchedule}
/>

Ta sẽ cần phải tính tất cả các callback truyền xuống và chủ động giảm bớt càng nhiều callback truyền xuống càng tốt để giữ cho code được "sạch". Tuy nhiên tôi cũng không nghĩ nó là 1 cách tốt, do component nên tự miêu tả nó.

Vậy làm cách nào để component tự miêu tả nó tốt, mà không phải truyền các callback?

Tôi đã thử React trong khi phát triển ứng dụng native iOS và rất thích thú với mô hình delegation của Apple.

https://cdn-images-1.medium.com/max/800/1*x6fZ_ugCB09ge5OPPqnYBQ.png

Tham khảo các khóa học lập trình online, onlab, và thực tập lập trình tại TechMaster

Bạn có thể thiết lập delegate, thứ sẽ phản hồi những phương thức. Do đó thay vì truyền tất cả method, bạn truyền tham chiếu của 1 object.

Để cụ thể hơn: giả sử bạn có 1 component TaskList và bạn muốn render vài Task, và những Task đó sẽ báo cho List khi có những sự kiện như hoàn thành 1 cú click, xóa hay lên lịch. Trong React ta sẽ viết code như dưới đây:

/* BEFORE */
class TaskList extends React.Component {
  constructor(props) {
    super(props);
    this.onTaskComplete = this.onTaskComplete.bind(this);
    this.onTaskDelete = this.onTaskDelete.bind(this);
    this.onTaskSchedule = this.onTaskSchedule.bind(this);
  }
  onTaskComplete() { /* completing stuff */ }
  onTaskDelete() { /* deleting stuff */ }
  onTaskSchedule() { /* scheduling stuff */ }

  render() {
    const { tasks } = this.props;

    return tasks.map(task => (
      <Task
        task={task}
        onTaskComplete={this.onTaskComplete}
        onTaskDelete={this.onTaskDelete}
        onTaskSchedule={this.onTaskSchedule}
      />
    ));
  }
}

Chúng tôi đã thử nhiều phương án nhưng tất cả đều kết thúc bằng việc truyền cả chùm phương thức như các bạn đã thấy ở trên. Tuy nhiên tôi sẽ tự hào mà giới thiệu với các bạn 1 cách tốt hơn với react-delegate, hội tụ những điểm hay nhất của 2 thế giới React và iOS. Với react-delegate, component TaskList của bạn sẽ như dưới đây:

/* AFTER */
class TaskList extends React.Component {
  constructor(props) {
    super(props);
  }
  onTaskComplete() { /* completing stuff */ }
  onTaskDelete() { /* deleting stuff */ }
  onTaskSchedule() { /* scheduling stuff */ }
  render() {
    const { tasks } = this.props;
    return tasks.map(task => (
      <Task
        task={task}
        delegate={this}
      />
    ));
  }
}

Trông đơn giản hơn hẳn. Tuy nhiên làm thế nào để thiết lập nó? Ý tưởng chính là: trong component Task, bạn sẽ định nghĩa tất cả các phương thức delegate mà bạn muốn, và component con Task sẽ gọi những phương thức đó khi cần.

import { setupDelegate } from 'react-delegate';
class Task extends React.Component {
  constructor(props) {
    super(props);
    setupDelegate(this, 'onTaskComplete', 'onTaskDelete', 'onTaskSchedule');
  }
  render() {
    const { task } = this.props;
    return (
      <div onClick={this.onTaskComplete}>
        {task.title}
      </div>
    )
  }
}

Giờ thì ta đã có những phương thức onTaskComplete, onTaskDelete và onTaskSchedule và ta có thể gọi chúng khi có các sự kiện onClick.

Còn việc lấy dữ liệu ngược về delegate như task id thì sao?

Bạn có thể tạo 1 handler middle như sau:

/* BEFORE */ #boring
import { setupDelegate } from 'react-delegate';
class Task extends React.Component {
  constructor(props) {
    super(props);
    setupDelegate(this, 'onTaskComplete');
  }
  onClick(){
    this.onTaskComplete(this.props.task.id);
  }
  render() {
    const { task } = this.props;
    return (
      <div onClick={this.onClick}>
        {task.title}
      </div>
    )
  }
}

Tuy nhiên ta sẽ bị lặp lại rất nhiều lần việc đơn giản hóa này. Do đó hãy thử 1 hướng như dưới đây:

/* AFTER */ #awesome
import { setupDelegate } from 'react-delegate';
class Task extends React.Component {
  constructor(props) {
    super(props);
    setupDelegate(this, 'onTaskComplete');
  }
  render() {
    const { task } = this.props;
    return (
      <div onClick={this.onTaskCompleteCached(task.id)}>
        {task.title}
      </div>
    )
  }
}

Mọi phương thức delegate sẽ có 1 hàm Cached được sử dụng để bind các tham số. 

Công việc thiết lập nho nhỏ này khá đơn giản đối với chúng tôi, cũng như cách chúng tôi sử dụng React. Mong rằng các bạn sẽ thấy hữu ích.

Bạn có thể xem thêm trên Github: https://github.com/swipesapp/react-delegate

Bài viết được dịch từ: https://medium.com/@tornoe/fixing-the-callback-hell-of-over-parenting-with-react-1aa7801a6856

Khóa học lập trình di động tại Techmaster:

Để cài đặt MacOSX lên phần cứng không phải Apple liên hệ chuyên gia cài Hackintosh:

  • Nguyễn Minh Sơn: 01287065634
  • Huỳnh Minh Sơn: 0936225565
  • Website: caidatmacos.com