在进行 React 技术栈的一些技术方案的「挖坟」过程中,有这样一个体会:为什么需要这样一个方案,是遇到了什么的问题,引入该方案会带来什么样的收益。任何技术方案的引入都对应了某些技术场景下遇到的问题。

Vue 的开发中引入了状态管理方案 Vuex,Vuex 的引入可有效降低组件之间的通信复杂度同时还提供了全局状态管理的能力,对于一个中大型平台来说,引入状态管理方案势在必得。

遇到的问题

讨论任何技术方案,都要从遇到的问题场景入手。在 Redux 的原作者 Dan Abramov
You Might Not Need Redux 一文中阐释了这个思想,是不是一定要用 Redux,不是的。什么场景下,什么问题下可以引用这个技术方案,是我们在考虑一些技术方案选型落地时的必须要考虑的一个问题。

我们遇到了什么问题?

Redux 中文文档「动机」一章中做了总结。大致有这么几个问题:

  1. 中大型单页应用的开发中,随着应用复杂度的提升,状态越来越多
  2. 状态的更改场景越来越多,包括用户交互,异步请求,组件之间的通信等
  3. 状态更改不可控,遇到问题不好排查

如何解决这些问题,就需要有一个统一的解决方案,最关键的一点:状态可控易管理

显然,Redux 就是来做这些事情的。

从 Redux 的三大原则中我们就可以看出来,Redux 就是完成状态的可控易管理。

一、 单一数据源(store)

整个应用的 state 被存储在一个 store 中,管理方便,对于服务端渲染有较大优势(就是大家常说的同构应用)。

二、 state 只读

state 不能直接被修改,必须 dispatch action 去修改。一个 action 就是一个js对象,描述即将发生的事情和要携带的参数。这个原则保证了状态的修改可控

三、通过纯函数(pure function)去修改 state

纯函数有个特点,整个过程中不产生任何副作用,固定的输入必产生固定的输出。此举保证了 state 变更的可追踪性。

以上,我们也就理解了 redux 这个库在 github 上的介绍

Predictable state container for JavaScript apps
(一个为 js 应用而生的可预测的状态容器)

Redux 都做了什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

import { createStore } from 'redux'

function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([action.text])
default:
return state
}
}

let store = createStore(todos, ['Use Redux'])

store.dispatch({
type: 'ADD_TODO',
text: 'Read the docs'
});

store.subscribe(() => {
// do your update
console.log(store.getState())
})

如上所示,这是一个最简单的 Redux 的使用示例。主要做了这几件事情:

  1. 创建一个全局 store,注意 redux 是单一数据源思想,因此在一个应用中使用一个 store 即可
  2. store 中获取 state 通过 store.getState()
  3. 想要改变 state 通过 dispatch 一个 action, action 是一个用来描述这次动作的 js 对象,里面包含了 action 的 type 和 payload,这里可以衍生出 action creator 的概念,一个函数专门用来产生 action。
  4. 一个 reducer 专门负责更新 state,通过判断不同的 action type,操作不同的state返回。注意这里不要直接操作 state, 而是 clone 一份,然后返回一个全新的 state,此处可以衍生出一些其他的数据结构的操作方案,如 immutable 方案。

通过以上我们可以发现,这是一个完全解耦的状态管理方案,不依赖于其他的任何库,一个纯 javascript 实现。在这一点上,和 Vuex 有着本质上的区别。也就是说 Vuex 只能是在 Vue 的全家桶中使用,是一个上层的状态管理方案。Redux 不同,它可以和任何 javascript 应用开发结合使用。那么在 React 中如何集成 Redux 呢,这就是接下来我们要讨论的 React Redux。

React Redux

React Redux 是一个在 React 中使用 Redux 方案的库。为什么引入一个状态管理方案,除了引入 Redux 还要引入 React Redux?

我们需要从 React Redux 所做的事情入手。React Redux做了什么?

  1. 每个组件都可以 dispatch action 更改 state
  2. 每个组件都只拿自己关心的 props 进行渲染
  3. 每个组件都可以获取全局的 store 实例
  4. 全局状态的更改的订阅,获取最新的 state
  5. 一些性能优化,避免一些不必要的 re-render 过程

综上几点,我们可以大致感知 React Redux 所做的一些事情,将一些通用的代码进行封装,暴露出 API 以简化在 React 中的使用。

有这么几个概念比较重要。

一、Provider

一个包含组件,接收 store 参数,子组件中都可以通过 connect 方法获得 store

二、connect()

一个高阶函数,返回一个 React 组件,主要做了以下两件事情:

  1. mapState,将 store 中 state 以 props 的形式传递给被 connect 的组件,
  2. mapDispatch,将 store 中的 action 以 props 的形式传递给被 connect 的组件,这其中还做了很多封装,可以允许我们直接传递 action creator,在使用的时候省去 dispatch 的过程

总结

以上仅仅是一个针对 Redux 和 React Redux 的大致轮廓。这个过程中还有几个问题没有讨论:

  1. Redux 中仅仅可以 dispatch 一个 action 去更新 state,如何将一些带副作用的过程进行封装,这就要讨论到 React 的中间件机制
  2. React Redux 中的性能优化都做了哪些事情

Redux 目前的维护者 Mark Erikson Idiomatic Redux: The History and Implementation of React Redux 一文中针对 React Redux 的 API 版本迭代过程中进行了很透彻的分析,推荐。

参考链接

  1. https://www.redux.org.cn/docs/introduction/Motivation.html
  2. https://blog.isquaredsoftware.com/2018/11/react-redux-history-implementation/
  3. https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367