網頁

2021/7/27

React Render Props簡介

React Render Props簡介。


簡介

React Render Props如字面所述是一個props,但與一般props的差別在於其值為一個返回可渲染物件的函式。

例如下面<Component/>render屬性接收的值data => <h1>Hello, {data}!</h1>為一個返回React element的函式,所以render即為一個render props。

<Component render={data => <h1>Hello, {data}!</h1>}>

Render Props只是React程式的一種設計手法而非API,所以上面的render也可用其他任意名稱。

Render Props用途為把狀態及行為抽離出來為component,讓其他component也可享用相同的特性。簡單說就是提高重用性。


範例

例如下面有兩個component HelloHi,兩者共有的特色是點擊時下方的Like數會增加。

App.js

import React from "react";
import "./App.css";

function App() {
  return (
    <div>
      <Hello name='John' />
      <Hi name='Mary' />
    </div>
  );
}

class Hello extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 0};

    this.handleLike = this.handleLike.bind(this);
  }

  handleLike = () => {
    let count = this.state.count;
    this.setState({count: ++count});
  };

  render() {
    return (
      <div>
        <h1 onClick={this.handleLike}>Hello, {this.props.name}!</h1>
        <span>Like:{this.state.count}</span>
      </div>
    );
  }
}

class Hi extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 0};

    this.handleLike = this.handleLike.bind(this);
  }

  handleLike = () => {
    let count = this.state.count;
    this.setState({count: ++count});
  };

  render() {
    return (
      <div>
        <h1 onClick={this.handleLike}>Hi, {this.props.name}!</h1>
        <span>Like:{this.state.count}</span>
      </div>
    );
  }
}

export default App;



Like依靠component的state及函式實現,要把兩者抽出為可共享的component就可利用render props的技巧。將上面Like的累加行為及狀態被抽出為Like component,HelloHi則成為Like的render props箭頭函式回傳的可渲染物件。原本HelloHi在被點擊時增加Like數的行為及記錄Like數的狀態都轉由Like提供。

Like.render()this.props.render()即為App()中的兩個<Like/>render props所指的箭頭函式。函式接收兩個參數,第一個參數為function handleLike,第二個參數為count數,並轉給函式返回的<Hello/><Hi/>作為onClick的事件處理器及顯示Like數的值。

App.js

import React from "react";
import "./App.css";

function App() {
  return (
    <div>
      <Like
        render={(handleLike, count) => (
          <Hello like={handleLike} count={count} name='John' />
        )}
      />
      <Like
        render={(handleLike, count) => (
          <Hi like={handleLike} count={count} name='Mary' />
        )}
      />
    </div>
  );
}

class Like extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 0};

    this.handleLike = this.handleLike.bind(this);
  }

  handleLike = () => {
    let count = this.state.count;
    this.setState({count: ++count});
  };

  render() {
    return <div>{this.props.render(this.handleLike, this.state.count)}</div>;
  }
}

class Hello extends React.Component {
  render() {
    return (
      <div>
        <h1 onClick={this.props.like}>Hello, {this.props.name}</h1>
        <span>Like:{this.props.count}</span>
      </div>
    );
  }
}

class Hi extends React.Component {
  render() {
    return (
      <div>
        <h1 onClick={this.props.like}>Hi, {this.props.name}</h1>
        <span>Like:{this.props.count}</span>
      </div>
    );
  }
}

export default App;


Render Props搭配HOC

Higher-Order Component(HOC) withLike()改寫如下,可看作是對<Hello/><Hi/>加上<Like/>的增強。

App.js

import React from "react";
import "./App.css";

function App() {
  const HelloWithLike = withLike(Hello, {name: "John"});
  const HiWithLike = withLike(Hi, {name: "Mary"});
  return (
    <div>
      <HelloWithLike />
      <HiWithLike />
    </div>
  );
}

// HOC
function withLike(Component, props) {
  return class extends React.Component {
    render() {
      return (
        <Like
          render={(handleLike, count) => (
            <Component like={handleLike} count={count} {...props} />
          )}
        />
      );
    }
  };
}

class Like extends React.Component {...}

class Hello extends React.Component {...}

class Hi extends React.Component {...}

export default App;


Render Props搭配props.children

上面提到render props可以為任意名稱的props,把Like.render()中的this.props.render改為this.props.children,利用props.children這特殊的props,如此render props的函式的就不用寫在<Component/>的屬性中,可以直接放在<Component>{...}</Component>間。

App.js

import React from "react";
import "./App.css";

function App() {...}

function withLike(Component, props) {
  return class extends React.Component {
    render() {
      return ( // move props.children from attribute to element content
        <Like>
          {(handleLike, count) => (
            <Component {...props} like={handleLike} count={count} />
          )}
        </Like>
      );
    }
  };
}

class Like extends React.Component {
  ...

  render() { // use props.children as render props
    return <div>{this.props.children(this.handleLike, this.state.count)}</div>;
  }
}

class Hello extends React.Component {...}

class Hi extends React.Component {...}

export default App;


參考github


Function Component改寫

把上面範例用Function Component改寫如下。

App.js

import React, {useState} from "react";
import "./App.css";

function App() {
  const HelloWithLike = withLike(Hello, {name: "John"});
  const HiWithLike = withLike(Hi, {name: "Mary"});
  return (
    <div>
      <HelloWithLike />
      <HiWithLike />
    </div>
  );
}

const withLike = (Component, props) => {
  return () => (
    <Like>
      {(handleLike, count) => (
        <Component {...props} like={handleLike} count={count} />
      )}
    </Like>
  );
};

const Like = (props) => {
  const [count, setCount] = useState(0);

  const handleLike = () => {
    setCount(count + 1);
  };

  return <div>{props.children(handleLike, count)}</div>;
};

const Hello = (props) => {
  return (
    <div>
      <h1 onClick={props.like}>Hello, {props.name}</h1>
      <span>Like:{props.count}</span>
    </div>
  );
};

const Hi = (props) => {
  return (
    <div>
      <h1 onClick={props.like}>Hi, {props.name}</h1>
      <span>Like:{props.count}</span>
    </div>
  );
};

export default App;


沒有留言:

張貼留言