React高阶组件入门与常用用法

简介

React 高阶组件简单来说就是一个没有副作用的高阶纯函数,且该函数接受一个组件作为参数,并返回一个新的组件。正式由于他也算是高阶函数,所以也可以使用类似柯里化的方式传组件或者其他参数。常用于多个组件有共同方法的时候。

1.基础理解

假如现在有以下两个组件,分别是对当前用户进行说明

  • 1.用户简介
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// UserProfiles.js
import * as React from 'react';
import axios from 'axios';
class UserProfiles extends React.Component {
state = {
username: ''
}
componentWillMount() {
axios.get('/username').then(item => {
this.setState({
username: item.data.username
})
}).catch(() => {
console.log('error')
});// 获取用户名
}
render() {
return (
<div>{this.state.username} 是xxx院院士,中国高级工程师,巴拉巴拉</div>
)
}
}
export default UserProfiles;
  • 用户退出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// goodbueuser.js
import * as React from 'react';
import axios from 'axios';
class UserProfiles extends React.Component {
state = {
username: ''
}
componentWillMount() {
axios.get('/username').then(item => {
this.setState({
username: item.data.username
})
}).catch(() => {
console.log('error')
});// 获取用户名
}
render() {
return (
<div>再见 {this.state.username}</div>
)
}
}
export default GoodbyeUser;

可以看到代码的冗余量。按照平时我们的编码习惯,冗余的代码我们会将它封装成一个函数。高阶组件就类似这么一个函数的概念。好,下面我们尝试用高阶组件来对这两个组件封装

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
//hoc.js
import * as React from 'react'
import axios from 'axios';
export interface Iprops {
username: string;
}
export default <T extends React.Component<Iprops>>(Com: new (props: Iprops) => T) => {
class NewComponent extends React.Component {
state = {
username: ''
}
componentWillMount() {
axios.get('/username').then(item => {
this.setState({
username: item.data.username
})
}).catch(() => {
console.log('error')
});// 获取用户名
}
render() {
return <Com username={this.state.username} />
}
}
return NewComponent
}
}

1
2
3
4
5
6
7
8
9
10
11
12
// UserProfiles.js
import React, { Component } from 'react';
import wrapHoc, { Iprops } from 'hoc';
class UserProfiles extends Component<Iprops> {
render() {
return (
<div>{this.props.username} 是xxx院院士,中国高级工程师,巴拉巴拉</div>
)
}
}
UserProfilesCom = wrapHoc(UserProfiles);
export default UserProfilesCom;
1
2
3
4
5
6
7
8
9
10
11
12
// goodbueuser.js
import * as React from 'react';
import wrapHoc, { Iprops } from 'hoc';
class GoodbyeUser extends React.Component<Iprops> {
render() {
return (
<div>再见 {this.props.username}</div>
)
}
}
GoodbyeUserCom = wrapHoc(GoodbyeUser);
export default GoodbyeUserCom;

看到没有,高阶组件就是把username通过props传递给目标组件了。目标组件只管从props里面拿来用就好了。这样子就完成了一个基本的高阶组件了

2.更改props

可以对参数组件的props进行增删改查的操作。我们平时使用的时候更多的是增加的操作,比如说我们常用的
react-router-dom中的Withrouter()。上面的例子就是属于更改props

通过高阶组件,我们可以在参数组件中使用this.props.username来调用这个应用的用户名了,这就类似withrouter中使用this.props.match.params.xxxx,来获取路由参数一样。

3.抽象state

这种用法一般用于不可控组件。比如我们熟悉的input,在react中我们需要通过onChange事件将输入的值绑定到state上,然后通过触发render函数渲染UI才能体现出来。通过高阶组件,我们可以抽象一个state,将不可控组件转变成可控组件。

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
function ppHOC(WrappedComponent) {
return class PP extends React.Component {
constructor(props) {
super(props)
this.state = {
name: ''
}
this.onNameChange = this.onNameChange.bind(this)
}
onNameChange(event) {
this.setState({
name: event.target.value
})
}
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onNameChange
}
}
return <WrappedComponent {...this.props} {...newProps}/>
}
}
}
// 链接:https://www.jianshu.com/p/0aae7d4d9bc1

然后这样使用它:(这里作者使用高阶组件的修饰器用法)

1
2
3
4
5
6
@ppHOC
class Example extends React.Component {
render() {
return <input name="name" {...this.props.name}/>
}
}

4.更多高阶函数用法

前面提到了,高阶组件就是一个高阶函数,那么我们使用很多关于高阶函数的用法。比如说柯里化

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
import React from 'react';
import axios from 'axios';
export interface Iprops {
data: string;
}
const HocCom = (key: string) => <T extends React.Component<Iprops>>(Com: new (props: Iprops) => T) => {
return class extends React.Component {
componentWillMount() {
axios.get('username/' + key).then(item => {
this.setState({
data: item.data.username
});
}).catch({
console.log('error')
})

}
render() {
return <Com data={this.state.data} />
}
}
}
class Com extends React.Component<Iprops> {
render() {
return <div>{this.props.data}</div>
}
}
export default HocCom('wenfei')(Com)

我们常见的这种柯里化的模式就是在antd的表单模块中,我们一般都会在模块的结尾写上

1
const WrappedNormalLoginForm = Form.create()(NormalLoginForm);

5.高阶组件使用注意事项

  • 不要在组件的render方法中使用高阶组件,尽量也不要在组件的其他生命周期方法中使用高阶组件
  • 如果需要使用被包装组件的静态方法,那么必须手动拷贝这些静态方法

6.高阶组件、组件继承的区别

高阶组件、组件组合、组件继承都可以用来扩充组件,赋予组件新的能力。
react的官网
(https://reactjs.org/docs/composition-vs-inheritance.html)
简单的说明了一下组件组合和继承的形式区别,而且在文章末尾建议我们不要使用继承的方式来扩充组件。为什么呢?我认为有以下几点。

1.灵活性

对于小项目,组件复用率小的情况下,其实三种方式都合适。但是对于大型的组件复用率高的项目,继承就会出现和明显的短板。比如说一个简单的List组件,通过继承方式获得可以支持上/下拉加载的组件ListA。有一天,项目经理说需要讲这个功能改成下拉刷新和向下滚动加载,发现ListA有部分功能相似,然后通过ListA继承修改代码获得了下拉刷新和向下滚动加载的组件ListB。第二天,项目经理又说要将功能做成仅仅滚动加载(例子,平时见不到这种需求)。what???我是要在原List组件上继承呢?还是在ListB上来继承呢?假如还有很多个list需要这种需求呢??都用来继承?这显得组件特别膨胀而且不灵活,耦合程度高。其实我们可以封装对应list的不同功能的高阶组件或组件组合,比如说下拉刷新的,向下滚动加载的,等等…然后拿来即用,传递想要功能的参数即可。灵活性大大提高。

2.单一性

还是上面的例子,我们发现我们通过继承获得了List\ListA\ListB等等组件。我们封装组件,一般是它实现了这个项目最基础而且可用的某个功能,相同功能引用相同组件即可。而继承的方式会获得基于这个基础组件的不同功能的组件,而且这些组件做的是同样事情,仅仅在方式上的不同。高阶组件/组件组合则可以保持这种react组件单一的特性。

3.复杂度

写组件继承,1.书写的麻烦,每个继承组件需要额外开销许多import和export。2.继承的时候还需要知道原组件实现了哪些方法?如果使用了ts还可能出现更多各种各样的错误。3.初始化 state、声明其它方法时要小心避免污染父类的state与方法。

参考文章

https://www.jianshu.com/p/0aae7d4d9bc1


作者简介: 张栓,人和未来大数据前端工程师,专注于html/css/js的学习与开发。