Redux状态数据流管理模型
在大约2017-2019年的时代,一提起React就必须要提Redux,已经到了“政治正确”的地步。然而,Redux不仅概念较为复杂,而且本身存在一定的设计缺陷,在那时候无脑使用Redux会导致工程中膨胀出巨量的样板代码,开发效率降低、认知成本极高,留下了无数“天坑”。后来虽然Redux推出了新版本的Redux Toolkit一定程度上优化了许多用法,但后来的绝大多数React工程都悄然但刻意的远离了Redux,并采用像Zustand这样的轻量级数据管理框架或不使用类似框架。不过实际上,Redux虽然存在很多问题,但它提出的很多思想还是非常值得学习了,虽然现在我们不一定会用Redux,但这些思想仍能让我们的架构变得严谨、有序和可控。
这篇笔记专门介绍一下什么是Redux,以及在React项目中Redux的意义。
什么是Redux
Redux是一种状态管理模式和架构思想,在组件状态(state)关联比较复杂的应用中,使用Redux模式管理这些组件状态,能够让我们的思路更加清晰。在旧Redux时代,虽然它不能让我们少写一些代码,相反使用Redux会让我们的项目变得更复杂,但是复杂并不是意味着困难,Redux能够让我们的编码思路变得简单。
什么时候需要用到Redux呢?比如一个React开发的应用需要能够更换皮肤,应用整体更换皮肤后每个组件都要更改其加载的CSS,如果不使用Redux,一种实现方式是从组件树的根组件开始,通过props和回调函数把“更换CSS”这个目的一层层传递给下层组件,想一想就太复杂了,不可能这样做。另一种方式是使用React组件的Context机制,Context内部的状态相当于暴露给所有子组件访问,通过Context能够较为简单的解决这个问题。
但是如果我们应用的组件之间的状态关联非常复杂,全部使用全局数据就会非常混乱,状态复杂时缺乏结构化管理,稍有不慎复杂的全局数据就会使代码变得难以维护,这时候许多人给出了各种各样的解决方案,其中Redux是曾经严谨、规范、同时也是“逼格”最高的一种,在历史上受到了广泛推崇。
Redux模式的编写
我们先不考虑React也不考虑Redux相关的库,我们使用最普通的JavaScript代码演示一下Redux模式的原理,这里我们直接使用JavaScript的DOM操作API代替React模拟两个简单的组件。
例子中,页面上定义了两个<div>,我们假装它们是两个组件。
index.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<title>Redux Demo1</title>
</head>
<body>
<div id="div1"></div>
<div id="div2"></div>
<script src="index.js"></script>
</body>
</html>
JavaScript代码中有相应的函数通过改变全局组件状态的方式,修改这两个“组件”的内容,但是我们使用的是Redux的写法,而不是直接修改。除此之外,还实现了通过回调函数实现当组件数据被改变时,自动触发组件的重新渲染的功能。
index.js
/**
* 重新渲染整个组件
* @param appState 新的组件状态数据
* @param oldAppState 之前的组件状态数据
*/
function renderApp(appState, oldAppState = {}) {
//判断一下,如果新的组件数据和原来的一样,就不要重新渲染了,这样做能提高性能,但是我们要比较的是两个不同对象的值,因此采用JSON.stringify()
if (JSON.stringify(oldAppState) !== JSON.stringify(appState)) {
console.log("app rendered");
//这里分别调用渲染组件div1和div2的函数
renderDiv1(appState.div1, oldAppState.div1);
renderDiv2(appState.div2, oldAppState.div2);
}
}
/**
* 重新渲染div1组件
* @param state
* @param oldState
*/
function renderDiv1(state, oldState = {}) {
//判断是否需要重新渲组件
if (JSON.stringify(state) !== JSON.stringify(oldState)) {
console.log("div1 rendered");
let componentDom = document.querySelector("#div1");
componentDom.innerHTML = state.text;
componentDom.style.color = state.color;
}
}
/**
* 重新渲染div2组件
* @param state
* @param oldState
*/
function renderDiv2(state, oldState = {}) {
if (JSON.stringify(state) !== JSON.stringify(oldState)) {
console.log("div2 rendered");
let componentDom = document.querySelector("#div2");
componentDom.innerHTML = state.text;
componentDom.style.color = state.color;
}
}
/**
* store可以看做全局状态数据的一个操作类,所有操作必须通过action对象通知store,让store去修改真正的数据(appState对象)
* @param reducer 定义如何修改全局状态数据的函数
* @returns {{getState: (function(): *), dispatch: dispatch, subscribe: subscribe}} store对象
*/
function createStore(reducer) {
let state = {
div1: {
text: "content of div1",
color: "red",
},
div2: {
text: "content of div2",
color: "blue",
},
};
let listeners = [];
return {
getState: () => state,
dispatch: (action) => {
state = reducer(state, action);
listeners.forEach((listener) => listener());
},
subscribe: (listener) => {
listeners.push(listener);
},
};
}
/**
* 定义改变全局组件数据的函数
* @param state 状态数据对象
* @param action 修改的动作类
*/
function reducer(state, action) {
switch (action.type) {
case "UPDATE_DIV1_TEXT":
return {
...state,
div1: {
...state.div1,
text: action.text,
},
};
case "UPDATE_DIV1_COLOR":
return {
...state,
div1: {
...state.div1,
color: action.color,
},
};
default:
return state;
}
}
let store = createStore(reducer);
//临时存储原来的状态
let oldState = store.getState();
//让store订阅重新渲染时的回调函数
store.subscribe(() => {
let newState = store.getState();
renderApp(newState, oldState);
oldState = newState;
});
//初始化页面,第一次手动调用绘制组件的函数
renderApp(store.getState());
//构造一个action对象,通知store去修改数据
store.dispatch({
type: "UPDATE_DIV1_COLOR",
color: "green",
});
显示效果如下图。

页面控制台日志输出如下。
app rendered
div1 rendered
div2 rendered
app rendered
div1 rendered
可以发现,修改组件状态数据的操作成功生效,修改页面上部分组件的状态数据后,并不是所有组件都被重新渲染了。
Redux实现分析
Redux内部的数据流可以按下图理解。

Redux中有这么几个重要的概念:
action:action只是一个数据结构,描述了一个事件(用以通知store该做出何种动作),而不提供具体操作的逻辑,action至少要有一个type属性
reducer:定义如何改变状态数据,对应上面代码中的stateChanger()函数,所有reducer可以匹配一个事件是否和自己相关,相关就拿走该事件修改状态数据,否则将状态数据原样返回。reducer其实就是这样的函数:(state, action)=>newState。
store:用于存储组件状态数据,对应上面代码中我们实现的store
Redux有三个核心原则:
- 单一数据源
- state只读
- 使用纯函数执行操作(reducer)
基于这些概念,Redux建立了严格的单向数据流模式:View通过dispatch触发action,reducer生成新的state,state再驱动视图更新,使得状态变化路径完全可追踪、可预测,这正是使用Redux的最大价值。
在React中使用Redux
上面说了一堆,其实就是为了说明Redux究竟是干什么用的。上面代码就是一个简易版的Redux库,但是我们实际使用中,Redux一般是和React一起使用的,因此我们需要redux、react-redux以及许多其他的可选依赖库,这些库又引入了一些新的API和新的概念,如何在React项目中方便的使用已经编写好的Redux,将在下篇笔记中介绍。