ReactJS Render Props封裝技巧

Render Props

一種在 React 組件之間使用一個值爲函數的prop, 在React 組件間共享代碼的簡單技術

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

隨着鼠標在屏幕上移動,在一個 p 標籤的組件上顯示它的 (x, y) 座標。

假設我們現在有一個在屏幕上跟隨鼠標渲染一張貓的圖片的 <Cat>組件。我們可能使用 <Cat mouse={{ x, y }} prop來告訴組件鼠標的座標以讓它知道圖片應該在屏幕哪個位置。

首先,你可能會像這樣,嘗試在 的內部的渲染方法渲染 組件:

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

這一方法對我們的具體用例來說能夠生效,但我們卻沒法實現真正的將行爲封裝成可重用的方式的目標。現在,每次我們在不同的用例中想要使用鼠標的位置,我們就不得不創建一個新的針對那一用例渲染不同內容的組件 (如另一個關鍵的 <MouseWithCat>)。

HOC 高階組件實現

class Cat extends React.Component {
    render() {
      const mouse = this.props.mouse
      return (
        <img src="https://ss3.baidu.com/-rVXeDTa2gU2pMbgoY3K/it/u=550398634,1506778242&fm=202" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
      );
    }
  }
function withMouse(Component) {
    return class 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}>
            <Component mouse={this.state} />
          </div>
          )
      }
    }
  } 
  export default withMouse(Cat);

render prop 實現

我們可以提供一個帶有函數 prop 的<Mouse>組件,它能夠動態決定什麼需要渲染的,而不是將 硬編碼到 <Mouse>組件裏,並有效地改變它的渲染結果。

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

        {/*
          Instead of providing a static representation of what <Mouse> renders,
          use the `render` prop to dynamically determine what to render.
        */}
        {this.props.render(this.state)}
      </div>
    );
  }
}

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

render props 與HOC( 高階組件)組合

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}>
        {this.props.render(this.state)}
      </div>
    );
  }
}
function withMouse(Component) {
  return class extends React.Component {
    render() {
      return (
        <Mouse render={mouse => (
          <Component {...this.props} mouse={mouse} />
        )}/>
      );
    }
  }
}
儘管之前的例子使用了 render,我們也可以簡單地使用 xxxx prop!

使用PureComponent的一個常見問題

class Mouse extends React.PureComponent {
 
}

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

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

在這樣例子中,每次 <MouseTracker>渲染,它會生成一個新的函數作爲<Mouse render>的 prop,因而在同時也抵消了繼承自 React.PureComponent 的<Mouse>組件的效果!

爲了繞過這一問題,有時你可以定義一個 prop 作爲實例方法,類似如下:

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

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

  renderTheCat(mouse) {
    return <Cat mouse={mouse} />;
  }

  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={this.renderTheCat} />
      </div>
    );
  }
}

新特性 React Hooks 也可解決類似問題 https://juejin.im/post/5be3ea136fb9a049f9121014

參考鏈接https://react.docschina.org/docs/render-props.html#%E5%9C%A8%E4%BA%A4%E5%8F%89%E5%85%B3%E6%B3%A8%E7%82%B9%EF%BC%88cross-cutting-concerns%EF%BC%89%E4%BD%BF%E7%94%A8-render-props

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章