轻松理解React高阶组件

一句话回忆

高阶组件就是一个函数,这个函数接受一个组件作为参数,把这个参数组件加工一下,然后返回一个新的组件。

前言

明天就要考 电力系统暂态分析 了,今天晚上竟然丝毫不慌,甚至还想写篇文档,我是真的牛皮。🙂

最简使用场景

定义一个高阶组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { Component } from "react";

//高阶组件
function a(WrappedComponent) {
//返回一个新组件
return class A extends Component {
render() {
return <div>
<div>这是A组件</div>
<WrappedComponent />
</div>
}
};
}

export default a;

上面例子中,a 函数就是一个高阶组件,它接收一个 WrappedComponent 组件作为参数,然后返回一个新的组件 A,可以看出,返回的新组件实质上就是对参数组件的一个加工

现在我们使用一下这个高阶组件:

1
2
3
4
5
6
7
8
9
10
import React, { Component } from "react";
import a from "./A";

class B extends Component {
render() {
return <div>这是B组件</div>;
}
}

export default a(B);

上面这段代码中,我们使用了高阶组件 a,此时的a(B) 实际上就是由 a 函数返回的新组件 A

高阶组件就是这么简单。😆

最简应用实例

为什么要使用高阶组件?

当有多个不同的组件需要执行同一份逻辑时,我们就可以使用高阶组件将重复的逻辑抽离出来。

高度相似的代码一定可以通过函数来进行整合缩减。

操作 props

我们给之前的高阶函数 a 加点功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, {Component} from 'react';

//高阶组件
function a(WrappedComponent) {

//定义一些属性
const someProps = {
name: '阿超',
age: '20'
};

return class A extends Component {
render() {
return <div>
{/* 注意:this.props 是传给这个新组件的属性*/}
<WrappedComponent {...this.props, ...someProps} />
</div>
}
}
}

export default a;

同时修改一下 B 组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { Component } from "react";
import a from "./A";

class B extends Component {
render() {
return <div>
<div>我的名字是:{this.props.name}</div>
<div>我的年龄是:{this.props.age}</div>
<div>我的性别是:{this.props.sex}</div>
</div>
}
}

export default a(B);

我们将 B 组件渲染一下看看:

1
2
3
4
5
6
7
8
9
10
11
import React from "react";
import "./App.css";
import B from "./components/B";

function App() {
return <div className="App">
<B sex="男" />
</div>
}

export default App;

在第一段代码中,我们定义了一个对象 someProps,然后使用扩展运算符将属性作为 props 传给参数组件(B)。

在第二段代码中,我们将 B 组件所有的 props 渲染出来。

第三段代码中,将 B 组件渲染到页面上,同时传入一个 sex 属性,注意,这里的 sex 属性实际上就是在高阶组件的 this.props 中。

要想删除 sex 属性,只需要在 a函数中将 sexthis.props 中分离出去即可。

这就是最简单的 props 操作。so easy!

抽取状态

我们将删除 a 函数之前 props操作的代码,然后加点新东西:

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
import React, {Component} from 'react';

//高阶组件
function a(WrappedComponent) {

return class A extends Component {
//给这个返回的新组件加个状态
constructor(props) {
super(props);
this.state = {
value: ''
};
}

handleChange = (e) => {
this.setState({
value: e.target.value
});
}

render() {
const newProps = {
value: this.state.value,
handleChange: this.handleChange
}
return <div>
<WrappedComponent {...this.props, ...newProps} />
</div>
}
}
}

export default a;

修改一下 B 组件:

1
2
3
4
5
6
7
8
9
10
11
12
import React, { Component } from "react";
import a from "./A";

class B extends Component {
render() {
return <div>
<input type="text" {...this.props} />
</div>
}
}

export default a(B);

第一段代码中,我们给高阶组件返回的新组件添加状态,同时定义一个可以修改这个状态的函数,最终将状态和函数一同传给参数组件。

第二段代码中,B 组件接收高阶组件传递过来的 value 状态和 handleChange 函数。

上面这两段代码,实际上就是一个受控组件,只不过是把受控组件的状态和逻辑抽离到高阶组件中

到此为止,通过 props 的操作和受控组件的状态抽离,我们可以理解出,高阶组件就是把组件的逻辑抽离到一个函数中,然后返回一个包含这部分逻辑的新组件,如果此时有多个组件同时需要这部分逻辑,只需要调用一下高阶组件即可。从另一个角度来看,这样还实现了逻辑与 UI 的分离。

与父组件的区别

既然是将组件中的逻辑抽取出来,那么父组件也可以完成这项任务啊,为什么还要高阶组件呢?

因为父组件侧重于 UI 层面的代码共用上,而高阶组件则侧重于逻辑方面的抽取。

结合高阶函数使用

再修改一下高阶组件 a ,此时 a 变成了高阶函数,它返回一个高阶组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, {Component} from 'react';

//高阶函数
const a = (title) => (WrappedComponent) => {

//定义一些属性
const someProps = {
name: '阿超',
age: '20'
}

return class A extends Component {
render() {
return <div>
<div>{title}</div>
{/* 注意:this.props 是传给这个新组件的属性*/}
<WrappedComponent {...this.props, ...someProps}/>
</div>
}
}
}

export default a;

修改一下组件 B

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { Component } from "react";
import a from "./A";

class B extends Component {
render() {
return <div>
<div>我的名字是:{this.props.name}</div>
<div>我的年龄是:{this.props.age}</div>
<div>我的性别是:{this.props.sex}</div>
</div>
}
}

export default a("这是高阶函数")(B);

这种用法会经常在在第三方库中出现,例如:ReduxMATERIAL-UI

Redux 中的应用就放在另一篇文章里吧,今天有点晚了,睡觉去。😴

这个主题的语法高亮,在 jsx 中的 return 后面竟然必须要有代码,这样写也太丑了。。

文章参考