你还不懂的React高阶指引?

1,103 阅读6分钟

一、render Props

其实render props就是能动态决定组件要渲染什么内容,话不多说,我们还是照样贴代码,

在具体场景和需求中去理解

// <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 来告诉组件鼠标的坐标以让它知道图片应该在屏幕哪个位置。如果按照我们的正常写法应该是这样

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 props

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实现

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组件变成了一个更具有扩展性的组件,就像是提供了一个接口

二、HOC(高阶函数)

如果说render Props能动态决定组件要渲染什么内容,那高阶函数(下面都简称为HOC吧)就是在不改变源组件的前提下,对源组件进行增强,然后返回一个新的高阶组件

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,即是被包裹组件

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