ReactJS: Render Props là gì?

Nói một cách dễ hiểu là Render Props là một thủ thuật giúp chúng ta sử dụng lại logic của một Component cho một Component khác. Render Props được sử dụng như một props với giá trị là một function.

Thông thường nó có dạng như sau:

<Component render={
   (data) => <OtherComponent item={data}></OtherComponent>
}>
</Component>

Để hiểu hơn về ứng dụng thực tiễn của Render Props, chúng ta cùng tìm hiểu một ví dụ sau nhé:

Giả sử, bạn có một Component để theo dõi và quản lý tọa độ của con chuột trên màn hình. Component này có nhiệm vụ là lưu lại tọa độ (x,y) của con chuột. Đơn giản nó được cài đặt như sau:

class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
        <h1>Move the mouse around!</h1>
        <p>The current mouse position is ({this.state.x}, {this.state.y})</p>
      </div>
    );
  }
}

Component Mouse có state là hai vị trị (x,y) của con chuột.

Sau đó, chúng ta lại có một Component khác muốn sử dụng tọa độ (x,y) này để làm gì đó. Ví dụ ở đây ta có một Component là Cat. Nó là một hình của một con mèo. Và yêu cầu ở đây là mỗi khi chúng ta di chuyển con chuột trên màn hình thì hình ảnh con mèo này(Cat) sẽ chạy theo vị trí con chuột.

Như vậy, chúng ta có sự liên quan giữa 2 component Mouse Cat. Bởi vì theo yêu cầu thì Cat sẽ phải sử dụng state của Mouse để di chuyển hình ảnh con mèo cho đúng.

Nếu chưa biết đến Render Props, chúng ta có thể viết Component Cat như sau:

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
    );
  }
}

class MouseWithCat extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
        <Cat mouse={this.state} />
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <MouseWithCat />
      </div>
    );
  }
}

Như các bạn thấy thì chúng ta tạo nên một component tên là MouseWithCat. Và trong hàm render của nó có một component khác là Cat. Component Cat nhận vào tọa độ (x,y) của con chuột để di chuyển hình ảnh con mèo. Nó hoạt động tốt đúng không nào.

Nhưng bài toán khác đặt ra ở đây là nếu chúng ta có một component khác là Dog, Pig, Chicken,… và cũng muốn làm những nhiệm vụ giống như Cat thì sao? Với giải pháp hiện tại thì chúng ta chỉ có cách copy component MouseWithCat và sử lại tương ứng cho những component mới: Dog, Pig, Chicken,…

Với cách làm này, chúng ta bị lặp code rất nhiều. Vậy có cách nào để sử dụng lại logic của component Mouse hay không? Và mục đích chính cũng là hạn chế lặp code. Code sẽ tập trung hơn khi mà chúng ta muốn thay đổi gì đó, chỉ cần sửa một chỗ.

Một giải pháp khác là dùng Render Props như sau:

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
    );
  }
}

class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>

        {/*
           Chúng ta có thể gọi hàm render ở đây với state có hai toạ độ (x, y).
Bạn có thể tự do khai báo một hàm render một component tuỳ thích và truyền vào như một props
        */}
        {this.props.render(this.state)}
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
       // props render là một hàm render component Cat
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

Như các bạn có thể thấy tất cả logic của Mouse đã được tách riêng ra một component. Cho nên khi chúng ta muốn sử dụng tọa độ (x,y) của Mouse chỉ việc truyền một Render Props như sau:

<Mouse render={mouse => (
  <Cat mouse={mouse} />
)}/>

Component Cat có thể thay bằng Dog, Pig, Chicken, …

Tên props render chúng ta có thể thay đổi thành tên mình thích như children, element, …

<Mouse children={mouse => (
  <p>The mouse position is {mouse.x}, {mouse.y}</p>
)}/>
<Mouse>
  {mouse => (
    <p>The mouse position is {mouse.x}, {mouse.y}</p>
  )}
</Mouse>

Tham khảo: https://reactjs.org/docs/render-props.html

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *