浅析组件化时代的前端状态管理(一):背景

为什么要写这个文章?

前端在工程化/组件化上近几年飞速发展, 生态已经相当成熟, 得益于 React Vue等前端框架的普及. 组件化的方式搭建前端页面已经是每一个前端的是标配技能了, 也极大降低了前端开发的入门门槛. 对于一个没有接触过前端的人来说(比如很多后端程序员), 只要大致了解es6语法, 看一下 React 或者 Vue 的官方文档, 使用 create-react-app 脚手架工具创建一个demo app, 基本上可以在半天内搭建出一个 简单的TODO APP, 配合上 Ant Design等流行的UI组件框架, 非专业的前端也能快速搭建起一套不错的后台管理系统.

UI上得益于开源的组件, 外观看上去非常现代并且简洁, UI设计语言也是高度一致, 这时候难免有些同学会觉得, 前端是在是太简单了. 不就用开源组件跟搭建积木一样把站点搭建起来嘛

举一个简单的例子,

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
// 需求: 写一个表单信息预览卡片, 需要实现 左侧展示表单, 右侧实时预览展示表单的填写数据
// 实现: 简单的实现就是一个输入框,加一个label展示, 可以实现实时输入并预览展示


import React, { PureComponent, Component } from "react";
import ReactDOM from "react-dom";

class App extends PureComponent {
constructor() {
super();
this.state = {
value: ""
};
this.onInput = this.onInput.bind(this);
}
onInput(event) {
this.setState({
value: event.target.value
});
}
render() {
return (
<>
<span>demo1_1: </span>
<input onChange={this.onInput} value={this.state.value} />
<span>{this.state.value}</span>
</>
);
}
}
export default App;

Edit front_end_state_manage_demo

当然真实的需求并不会如此简单, 可能表单的输入框有十几项需要填写, 可能表单的预览展示也是有非常多的个性化展示, 在组件化思维的影响下, 一般比较好的选择是把 App 拆成多个子组件

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
57
58
59
60
61
62
63
64
65
// 需求: 写一个表单信息预览卡片, 需要实现 左侧展示表单, 右侧实时展示表单的填写数据预览
// 实现: 把 App拆成两个组件, 分别为表单组件和 表单预览组件


import React, { PureComponent, Component } from "react";
import ReactDOM from "react-dom";

// 表单 输入框组件
class FormInput extends PureComponent {
constructor() {
super();
this.state = {
value: "2234"
};
this.onInput = this.onInput.bind(this);
}

onInput(event) {
this.setState({
value: event.target.value
});
}
render() {
return (
<>
输入组件:
<input onChange={this.onInput} value={this.state.value} />
<span>{this.state.value}</span>
</>
// 更多其他的输入框
);
}
}

// 表单预览组件
class FormPreview extends PureComponent {
render() {
return (
<>
预览部分:
<label>{this.props.value}</label>
</>
// 更多其他的渲染逻辑
);
}
}

class App extends Component {
constructor() {
super();
}
render() {
return (
<div>
<span>demo1_2: </span>
<br />
<FormInput />
<hr />
<FormPreview />
</div>
);
}
}

export default App;

Edit front_end_state_manage_demo

这时候问题来了, FormInput组件里面的数据如何传递到 FormPreview中, 所以实际上上述的例子是无法实现需求的. 如何解决? 一个最简单的方法就是把value存放在App组件中, 然后 FormInput FormPreview 共用同一个对象.
我们这个场景比较简单,可能这样子也行得通, 假如组件的嵌套层级很深. 比如一颗深度为5的组件二叉树, 最底下的叶子节点组件要相互通信怎么办? 难道要把叶子节点的数据状态存在根节点组件上么? 如果这样做, 必然会导致非常冗余的props传递, 所以肯定不会直接这么搞的, 正常情况下大家都会默默打开搜索引擎, 输入 “React, 组件通信, 状态管理” 关键词, 搜索的结果大致会分为如下几类:

第一类: 原生的方式
  • 父组件向子组件通信: props
  • 子组件向父组件通信: 回调函数/自定义事件
  • 跨级组件通信: 层层组件传递props或者使用contextAPI
  • 没有嵌套关系组件之间的通信: 自定义事件
第二类: 第三方状态管理库

引入Mobx Redux等状态管理库

第三类: React Hooks

使用React Hooks, 例如useState/useReducer/useContext

下面将循序渐进的介绍上述几种解决方案.