React学习——HOC

一、HOC高阶组件

1.介绍

高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件

1
const NewComponent = higherOrderComponent(OldComponent)

构建了一个新的组件类 NewComponent,然后把传进入去的 WrappedComponent 渲染出来

1
2
3
4
5
6
7
8
9
10
11
import React, { Component } from 'react'

export default (WrappedComponent) => {
class NewComponent extends Component {
// 可以做很多自定义逻辑
render () {
return <WrappedComponent />
}
}
return NewComponent
}

2.使用

  • 普通函数包裹
  • 装饰器
    1
    2
    3
    4
    5
    @decorator
    class A {}
    // 等同于
    class A {}
    A = decorator(A) || A;

3.分类

  • 代理方式

    • 操纵prop:在 render 函数中, this.props 包含新组件接收到的所有 prop,最简单的方式当然是把 this.props 原封不动地传递给被包裹组件,当然,高阶组件也可以增减、删除、修改传递 给包裹组件的 props列表
    1
    2
    3
    4
    5
    6
    const addNewProps = (WrappedComponent, newProps) => { return class WrappingComponent extends React.Component {
    render() {
    return <WrappedComponent {... this.props} {... newProps} />
    }
    }
    }
    • 访问ref:能够获得被包裹组件的直接应用 ref,然后就可以根据 ref直接操纵被包裹组件的实例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      const refsHOC = (WrappedComponent) => {
      return class HOCComponent extends React . Component {
      constructor () {
      super (... arguments) ;
      this.linkRef = this.linkRef.bind(this);
      linkRef(wrappedinstance} { this._root = wrappedinstance ;
      }
      render(} {
      const props= {. .. this .props , ref : this.linkRef} ; return <WrappedComponent {.. .props}/>;
      }
      };
      };
    • 抽取状态:类似于connect

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const doNothing = () => ({}) ;
      function connect(mapStateToProps=doNothing, mapDispatchToProps=doNothing) { return function(WrappedComponent) {
      class HOCComponent extends React.Component {
      //在这里定义HOCComponent的生命周期函数
      };
      HOCComponent.contextTypes = {
      store: React.PropTypes.object
      }
      return HOCComponent;
      };
      }
    • 包装组件: JSX 中完全可以引人其他的元素,甚至可以 组合多个 React 组件,这样就会得到更加丰富多彩的行为

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      const styleHOC = (WrappedComponent, style) => {
      return class HOCComponent extends React.Component {
      render() { return (
      <div style={style}>
      <WrappedComponent {... this.props}/>
      </div>
      );
      }
      };
      };
  • 继承方式

    1
    2
    3
    4
    5
    6
    7
    8
    function removeUserProp(WrappedComponent) {
    return class NewComponent extends WrappedComponent {
    render() {
    const {user, ... otherPropsl = this.props; this .props = otherProps;
    return super.render();
    }
    };
    }

    代理方式和继承方式最大的区别,是使用被包裹组件的方式
    在代理方式下 WrappedComponent经历了一个完整的生命周期,但在继承方式下 super.render 只是一个生命周期中的一个函数而已;在代理方式下产生的新组件和参数组件是两个不同的组件,一次渲染,两个组件都要经历各自的生命周期,在继承方式下两者合二为一,只有一个生命周期

    • 操纵prop:可以使用React.cloneElemen,不建议,直接使用代理方式即可
    • 操纵生命周期函数:因为继承方式的高阶函数返回的新组件继承了参数组件,所以可以重新定义任何一个React组件的生命周期函数

二、实际应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import React, { Component } from 'react';
import { connect } from 'dva';

export default WrappedComponent => {
function getFormInputs(queryParams) {
if (queryParams === null) {
return null;
}
return Object.keys(queryParams).map(name => (
<input name={name} type="hidden" value={JSON.stringify(queryParams[name])} key={name} />
));
}

@connect(({ routing: { location: { query } }, listPage: { selectedRows } }) => ({
query,
selectedRows,
}))
class ExcelExportComponent extends Component {
saveFormExportRef = formExportRef => {
this.formExportRef = formExportRef;
};

render() {
const {
query: { resumeId },
selectedRows,
} = this.props;
const hideForm = {
display: 'none',
};
let resumeIdList;
if (selectedRows.length > 0) {
resumeIdList = selectedRows.map(item => item.resumeId);
} else {
resumeIdList = [];
resumeIdList.push(resumeId);
}
return (
<div>
<WrappedComponent exportRef={() => this.formExportRef.submit()} />
<form
ref={this.saveFormExportRef}
action={`${ApiPrefix}/pc/recruit/resume/exportExcel`}
style={hideForm}
method="post"
target="resume_iframe"
>
{getFormInputs({ resumeIdList })}
</form>
<iframe title="iframe" id="resume_iframe" name="resume_iframe" style={hideForm} />
</div>
);
}
}
return ExcelExportComponent;
};

在需要使用的组件进行绑定

1
2
3
4
5
6
7
8
9
import ExcelExport from '../ExcelExport';

@ExcelExport
//……
const { exportRef } = this.props;
//在相应的按钮事件
<Button type="primary" onClick={exportRef}>
导出
</Button>

三、约定

  1. 将不相关的props属性传递给包裹组件。高阶组件给组件添加新特性。他们不应该大幅修改原组件的接口(译者注:应该就是props属性)。预期,从高阶组件返回的组件应该与原包裹的组件具有类似的接口。

  2. 最大化使用组合。

    例子:connect 是一个返回高阶组件的高阶函数

    1
    2
    // React Redux's `connect`
    const ConnectedComment = connect(commentSelector, commentActions)(Comment);
    1
    2
    3
    4
    5
    // connect是一个返回函数的函数(译者注:就是个高阶函数)
    const enhance = connect(commentListSelector, commentListActions);
    // 返回的函数就是一个高阶组件,该高阶组件返回一个与Redux store
    // 关联起来的新组件
    const ConnectedComment = enhance(CommentList);
  3. 包装显示名字以便于调试

四、注意事项

  1. 不要在render函数中使用高阶组件
  2. 必须将静态方法做拷贝
  3. Refs属性不能传递

五、拓展——以函数为子组件

高阶组件缺点:能不能把一个高阶组件作用于某个组件X,要先看一下这个组件X是不是能够接受高阶组件传过来的props
举例:

1
2
3
4
5
6
7
8
9
const loggedinUser = ’ mock user ’;
class AddUserProp extends React . Component { render{) {
const user = loggedinUser ;
return this.props.children(user);
}
}
AddUserPr。p.propTypes = {
children: React.PropTypes.func.isRequired
}

使用:

1
2
3
4
5
<AddUserProp>
{
(user) => <Bar currentUser={user} />
}
</ AddUserProp>

以后想到了实际应用再补充

参考

高阶组件
阮一峰ES6教程
高阶组件(Higher-Order Components)
深入浅出React和Redux