本文章重点讲述React高阶组件的使用场景,附带讲述高阶函数,因为两者很类似。对比起来更容易理解
什么是高阶函数:一个函数的参数是一个函数,或者 函数的返回值是一个函数,我们称这类函数是高阶函数。
例如:
const Mozi = function(name1){
return function (name2){
return name1 + ' ' + name2;
};
};
上面函数的执行过程如下
> Mozi('墨子')
<- function(name2){
return name1 + ' ' + name2;
}
> Mozi('墨子')('工程')
<- '墨子 工程'
执行函数Mozi('墨子') 返回的是一个函数,我们执行 Mozi('墨子')('工程') 相当于执行了返回的函数
高阶函数实际的作用是什么?
以下面例子,我们要把数组的每一项变成mozi_n的方式
我们一般会用下面的方法进行实现
function format(item) {
return `mozi_${item}`;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var result = [];
for (var i=0; i<arr.length; i++) {
result.push(format(arr[i]));
}
console.log(result) // ["mozi_1", "mozi_2", "mozi_3", "mozi_4", "mozi_5", "mozi_6", "mozi_7", "mozi_8", "mozi_9"]
如果用高阶函数map来实现,如下
function format(item) {
return `mozi_${item}`;
}
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(format); // ["mozi_1", "mozi_2", "mozi_3", "mozi_4", "mozi_5", "mozi_6", "mozi_7", "mozi_8", "mozi_9"]
上例中map把对数组的遍历进行了抽象封装,我们在对数组进行操作的时候不用关心map内部是怎么实现的,只需要将我们对数组的每一项的处理函数传进去,就会返回处理后的新数组。
这样做的好处是简洁,通用,我们再对数组进行其他操作的时候就不需要重复写for循环来处理,例如
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// 把数组每一项转换成字符串,只需要一行代码
arr.map(String); // ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
上面两个例子来简单描述了一下什么是高阶函数,以及简单用法。(有空再看一下MOZI中对redux connect的封装)
什么是React高阶组件:一个组件的参数是组件,并且返回值是一个组件,我们称这类组件为高阶组件
React 中的高阶组件主要有两种形式:属性代理 和 反向继承。
属性代理: 是 一个函数接受一个 WrappedComponent 组件作为参数传入,并返回一个继承了 React.Component 组件的类,且在该类的 render() 方法中返回被传入的 WrappedComponent 组件
属性代理形如:
function MoziComponent(WrappedComponent) {
return class extends React.Component {
render() {
return <WrappedComponent {...this.props} />;
}
};
}
属性代理类高阶组件可以做什么?
- 操作 props
- 用其他元素包裹传入的组件 WrappedComponent
- 通过 ref 访问到组件实例
- 抽离 state
一:操作 props(增加新props)
function MoziComponent(WrappedComponent) {
return class extends WrappedComponent {
render() {
const newProps = {
mozi: 'mozi'
}
return <WrappedComponent {...this.props,..newProps} />
}
};
}
二:用其他元素包裹传入的组件 WrappedComponent
function MoziComponent(WrappedComponent) {
return class extends WrappedComponent {
render() {
const newProps = {
mozi: 'mozi'
}
return <div style={{display: 'block'}}>
<WrappedComponent/>
</div>
}
};
}
三:通过 ref 访问到组件实例(在高阶组件中获取到组件实例可以直接操作dom,比如input,但是实用场景很少,有些很难用state去实现的复杂动画之类的效果,用ref直接操作dom更方便一些)
import React, { Component } from 'react';
const header_hoc = WrappedComponent => {
return class header_hocmozi extends Component {
renderHeader = () => {
return <div style={{margin:'0 auto',textAlign:'center'}}>
全局头部
</div>
}
getRef(ComponentRef) { // 看这里
if(ComponentRef && ComponentRef.home) {
console.log('wrappedComponentInstance-----',ComponentRef.home)
}
}
render() {
const __props ={...this.props,ref:this.getRef.bind(this) }
return <div>
{ this.renderHeader() }
<WrappedComponent {...__props}/>
</div>
}
}
}
export default header_hoc;
四:抽离 state(因为属性代理类的高阶组件可以造作一切react有的方法或者属性,但是不建议在高阶函数里操作state,因为state属于一个组件的私有行为,一旦在高阶组件里做了一些state的更改删除之类的,容易把原有组件覆盖或者误操作,出了问题也很难去定位。)
反向继承:是 一个函数接受一个 WrappedComponent 组件作为参数传入,并返回一个继承了该传入 WrappedComponent 组件的类,且在该类的 render() 方法中返回 super.render() 方法。
是因为传入组件被动地被返回的Mozi继承,而不是 WrappedComponent 去继承 Mozi。通过这种方式他们之间的关系倒转了,所以叫反向继承。
反向继承形如:
function MoziComponent(WrappedComponent) {
return class Mozi extends WrappedComponent {
render() {
return super.render();
}
};
}
反向继承因为继承了传入的组件,所以你可以获取传入组件(WrappedComponent)的state,props,render,以及整个生命周期
可以用反向继承高阶组件做什么?
- 渲染劫持
- State 抽象和更改
1 - 条件渲染【渲染劫持】
如下,通过 props.isLoading 这个条件来判断渲染哪个组件
const withLoading = (WrappedComponent) => {
return class extends WrappedComponent {
render() {
if(this.props.isLoading) {
return <Loading />;
} else {
return super.render();
}
}
};
}
2 - 修改要渲染的元素【渲染劫持】
以下demo只是展示了如何通过修改element,意味着你可以在高阶组件中随意控制返回你要渲染的元素。
比如:我们的权限控制有页面级别的,也有细粒度的按钮级别的,渲染劫持类高阶组件就最适合细粒度的把控每个小组件的渲染权限,根据接口返回是否要展示某一个按钮 or 某一个组件。(并且不侵入具体业务代码,如果没有用高阶组件去抽离这部分细粒度的权限控制你就需要在业务代码中去写,会变得很难维护)
/*
组件修改,组件级别权限控制
*/
const edit_hoc = WrappedComponent => {
return class extends WrappedComponent {
render() {
const tree = super.render();
let props = {
...tree.props,
};
props.children.map(item=>{
item && item.type == 'input' && (item.props.value = '墨子工程')
})
const newTree = React.cloneElement(tree, props, tree.props.children);
return newTree;
}
};
};
export default edit_hoc;
实用场景
1- 组件渲染性能追踪
//performance_hoc.js
const hoc = WrappedComponent => {
return class performance_hoc extends WrappedComponent {
constructor(props) {
super(props);
this.start = 0;
this.end = 0;
}
componentWillMount() {
super.componentWillMount && super.componentWillMount();
this.start = Date.now();
}
componentDidMount() {
super.componentDidMount && super.componentDidMount();
this.end = Date.now();
console.log(`%c ${WrappedComponent.name} 组件渲染时间为 ${this.end - this.start} ms`,'font-size:20px;color:red;');
}
render() {
return super.render()
}
};
};
export default hoc;
使用方式
import React, { Component } from 'react';
import hoc from './performance_hoc';
@hoc
export default class Performance extends Component {
render() {
return <div>墨子工程</div>
}
}
2 - 权限控制
import React, { Component } from 'react';
const auth_hoc = role => WrappedComponent => {
return class extends React.Component {
state = {
permission: false,
loading: true
}
showLoading = (isShow) => {
this.setState({
loading: isShow,
});
}
checkAuth = () => { // 模拟返回角色的api
return new Promise((reslove,reject)=>{
this.showLoading(true)
setTimeout(_=>{
reslove('admin')
this.showLoading(false)
},2000)
})
}
async componentWillMount() {
const __role = await this.checkAuth();
this.setState({
permission: __role == role,
});
}
render() {
const { loading, permission } = this.state
if(loading) {
return <div>loading...</div>
}
if (permission) {
return <WrappedComponent {...this.props} />;
} else {
return (<div>您没有权限查看该页面!</div>);
}
}
};
}
export default auth_hoc;
使用(比如:产品需求是这个页面只有admin方式的才能展示 or 只有vip方式的才能展示,就可以通过高阶组件去抽离,你也不需要每一个权限都写一个高阶组件,只需要 传递不同的权限去接口验证就ok)
import React, { Component } from 'react';
import auth_hoc from './auth_hoc';
@auth_hoc('admin') //vip
export default class AuthPage extends Component {
constructor(...args) {
super(...args);
}
render() {
return (
<div className="Home">
墨子工程
</div>
);
}
}
3 - 组件&逻辑复用
//header_hoc.js
import React, { Component } from 'react';
const header_hoc = WrappedComponent => {
return class header_hocmozi extends Component {
renderHeader = () => {
return <div style={{margin:'0 auto',textAlign:'center'}}>
全局头部
</div>
}
render() {
return <div>
{ this.renderHeader() }
<WrappedComponent {...this.props}/>
</div>
}
}
}
export default header_hoc;
使用
import React, { Component } from 'react';
import header_hoc from './header_hoc';
@header_hoc
export default class Home extends Component {
render() {
return (
<div className="Home">
首页
</div>
);
}
}
高阶组件其实就是装饰器模式在 React 中的实现:通过给函数传入一个组件,在函数内部对该组件进行功能的增强,最后返回这个组件
总结:
- 高阶组件 不是组件,是 一个把某个组件转换成另一个组件的 函数
- 高阶组件的主要作用是 代码复用
- 高阶组件是 装饰器 在 React 中的实现