你還不懂的React高階指引?

1、render Props

其實render props就是能動態決定組件要渲染什麼內容,話很少說,咱們仍是照樣貼代碼,react

在具體場景和需求中去理解es6

// <Mouse> 組件封裝了咱們須要的行爲...
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}>

        {/* ...但咱們如何渲染 <p> 之外的東西? */}
        <p>The current mouse position is ({this.state.x}, {this.state.y})</p>
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>移動鼠標!</h1>
        <Mouse />
      </div>
    );
  }
}複製代碼

在這裏咱們有一個mouse組件,能夠監測到鼠標的位置,而後咱們引用了這個組件,在外面加個一個h1標題,但咱們仍是沒能去改變mouse組件裏面的內容,如何渲染mouse組件出p以外的內容,咱們繼續往下看,舉個例子,假設咱們有一個 <Cat> 組件,它能夠呈現一張在屏幕上追逐鼠標的貓的圖片。咱們或許會使用 <Cat mouse={{ x, y }} prop 來告訴組件鼠標的座標以讓它知道圖片應該在屏幕哪一個位置。若是按照咱們的正常寫法應該是這樣redux

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

        {/*
          咱們能夠在這裏換掉 <p> 的 <Cat>   ......
          可是接着咱們須要建立一個單獨的 <MouseWithSomethingElse>
          每次咱們須要使用它時,<MouseWithCat> 是否是真的能夠重複使用.
        */}
        <Cat mouse={this.state} />
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>移動鼠標!</h1>
        <MouseWithCat />
      </div>
    );
  }
}複製代碼

這裏咱們至關因而重寫了組件,那若是咱們使用render propsbash

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src={CatJpg} 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)}  //這裏的render其實就是Mouse的prop
      </div>
    );
  }
}

export default class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>移動鼠標!</h1>
        <Mouse render={mouse => (
          <Cat mouse={mouse}/>  //mouse參數就是從上面傳來的
        )}/>
      </div>
    );
  }
}複製代碼

這裏使用render props咱們完美實現了,但咱們是否可使用別的方法呢,這裏咱們用children實現app

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src={CatJpg} 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.children(this.state)}
      </div>
    );
  }
}

export default class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>移動鼠標!</h1>
        <Mouse>
          {(mouse) => <Cat mouse={mouse}/>}
        </Mouse>
      </div>
    );
  }
}複製代碼

既然講到children,也能夠看看這個函數

看到這裏就有人說了,那你用render Props其實也是改變了源組件,但其實並不相同,這裏能夠理解成咱們把Mouse組件變成了一個更具備擴展性的組件,就像是提供了一個接口工具

2、HOC(高階函數)

若是說render Props能動態決定組件要渲染什麼內容,那高階函數(下面都簡稱爲HOC吧)就是在不改變源組件的前提下,對源組件進行加強,而後返回一個新的高階組件post

import React, {Component} from 'react';

function withHeader(title) {
  return function (WrappedComponent) {
    return class HOC extends Component {
      render() {
        return <div>
          <div className="demo-header">
            {title
              ? title
              : '我是標題'}
          </div>
          <WrappedComponent {...this.props}/>
        </div>
      }
    }
  }
}


//@withHeader   //在這裏可使用es7的decorator,es7的decorator能夠在最後的相關連接觀看
class Demo extends Component {
  render() {
    return (
      <div>
        我是一個普通組件
      </div>
    );
  }
}

export default withHeader('title')(Demo);複製代碼

Demo就是源組件,withHeader就是HOC,withHeader返回一個高階組件優化

withHeader的第一個參數是用來決定組件中的title,而第二個參數則是WrappedComponent,便是被包裹組件ui

2.1 屬性代理

import React, {Component} from 'react';

function withHeader(title) {
  return function (WrappedComponent) {
    return class HOC extends Component {

      render() {
      const newProps = {
        test:'hoc'
      }
      //即是將HOC中定義的props傳到wrapperComponent中

        return <div>
          <div className="demo-header">
            {title
              ? title
              : '我是標題'}
          </div>
          <WrappedComponent {...this.props} {...newProps}/>
        </div>
      }
    }
  }
}


//@withHeader   //在這裏可使用es7的decorator,es7的decoratro咱們會在後面詳解
class Demo extends Component {
  render() {
    return (
      <div>
        我是一個普通組件
      </div>
    );
  }
}

export default withHeader('title')(Demo);
複製代碼

2.2 反向繼承

反向繼承應該是一個繼承的做用,高階函數能夠去繼承被包裹組件

mport React, {Component} from 'react';

function withHeader(WrappedComponent) {
  return class HOC extends WrappedComponent  {
    //其實這裏就是繼承了wrapperComponent(被包裹組件)
    componentDidMount(){
      console.log(this.state);  
    // {second: 0} 這裏打印出了被包裹組件的state 
    }
    render() {
      return super.render()
      //這個代碼我也不是很懂,就是es6的class中的語法
    }
  }
}

class Demo extends Component {
  constructor(props) {
    super(props);
    this.state = { seconds: 0 };
  }
  render() {
    return (
      <div>
        我是一個普通組件
      </div>
    );
  }
}

export default withHeader(Demo);複製代碼

2.3 組合多個高階組件

好比你要對一個組件進行多個加強或者削弱,這時候你可能須要用到多個

import React, {Component} from 'react';
import _ from 'lodash';

function withHeader(WrappedComponent) {
  return class HOC extends Component {
    render() {
      return <div>
        <div className="demo-header">
          我是標題
        </div>
        <WrappedComponent {...this.props}/>
      </div>
    }
  }
}

function withHandSome(WrappedComponent) {
  return class HOC extends Component {
    render() {
      return <div>
        <div className="demo-header">
          我說帥哥
        </div>
        <WrappedComponent {...this.props}/>
      </div>
    }
  }
}


@withHeader
@withHandSome  
//這裏用es7 decorator一樣能實現,可是這樣寫太複雜了,咱們可使用compose函數,許多第三方庫都提供了
 compose 工具函數,包括 lodash (好比 lodash.flowRight), Redux和 Ramda
                 

class Demo extends Component {
  render() {
    return (
      <div>
        我是一個普通組件
      </div>
    );
  }
}

export default withHandSome(withHeader(Demo));  //其實就是方法一層套一層複製代碼

下面咱們試着將組合一下

import React, {Component} from 'react';
import _ from 'lodash';

function withHeader(WrappedComponent) {
  return class HOC extends Component {
    render() {
      return <div>
        <div className="demo-header">
          我是標題
        </div>
        <WrappedComponent {...this.props}/>
      </div>
    }
  }
}

function withHandSome(WrappedComponent) {
  return class HOC extends Component {
    render() {
      return <div>
        <div className="demo-header">
          我說帥哥
        </div>
        <WrappedComponent {...this.props}/>
      </div>
    }
  }
}

const enhance = _.flowRight([withHeader, withHandSome])
//這裏我用了lodash的compose函數,compose函數的本質其實就是
compose(f, g, h) 等同於 (...args) => f(g(h(...args)))

@enhance
export default class Demo extends Component {
  render() {
    return (
      <div>
        我是一個普通組件
      </div>
    );
  }
}

這裏還要提到redux的connect,connect也是一個返回高階組件的高階函數  
const ConnectedComment = connect(commentSelector, commentActions)(CommentList);
  //等同於下面這種寫法
// connect 是一個函數,它的返回值爲另一個函數。
const enhance = connect(commentListSelector, commentListActions);
// 返回值爲 HOC,它會返回已經鏈接 Redux store 的組件
const ConnectedComment = enhance(CommentList);
複製代碼

2.4 總結

HOC是es7Decorator模式在React的一種實現,它能夠抽離公共邏輯,像洋蔥同樣層層疊加給組件,每一層職能分明,能夠方便地抽離與增添。在優化代碼或解耦組件時,能夠考慮使用高階組件模式。

推薦閱讀

React之不簡單的Children

相關文章
相關標籤/搜索