Skip to content

AHABHGK

Redux 源码分析

SourceCode1 min read

Table of Contents

随着前端应用逐渐变大,状态也越来越复杂,需要一种状态管理的方案,Redux 就是其中一种

总结起来 React 状态管理方案就分为两类:

  • 外部 Model,通过事件修改状态,组件通过监听订阅状态(通过 Context 连接 React)

    • Redux

    • Mobx

    • EventBus

    • hox

  • 通过 React Context 提升状态(包括修改状态的方法),在组件中拿到并使用 Context

    • unstated

    • unstated-next

    • reunx

使用外部 Model 的模式由于不依赖于 React,所以不仅适用于 React,在各个框架甚至原生应用中都可以使用;而第二种依赖于 React Context 仅适用于 React 应用,但是十分轻量

MVC(backbone.js...)由于当应用变得很大时,会有多个 View 和多个 Model,(双向的)数据流就会变的非常混乱

MVC-bad

所以出现了 Flux,以单向数据流管理状态,React 推崇的核心也是单向数据流,Flux 中单向数据流可以看作是在其整体架构上的延伸,Redux 是也不是 Flux,它遵循了 Flux 的单向数据流的理念,同时简化了 Flux

different

Flux/Redux 这种以单向数据流管理状态更像是一种设计模式

👀 Redux 对比 Mobx

Mobx 和 Redux 的目标都是管理好应用状态,但是最根本的区别在于对数据的处理方式不同。

Redux 认为,数据的一致性很重要,为了保持数据的一致性,要求Store 中的数据尽量范式化,也就是减少一切不必要的冗余,为了限制对数据的修改,要求 Store 中数据是不可改的(Immutable),只能通过 action 触发 reducer 来更新 Store。

Mobx 也认为数据的一致性很重要,但是它认为解决问题的根本方法不是让数据范式化,而是不要给机会让数据变得不一致。所以,Mobx 鼓励数据干脆就“反范式化”,有冗余没问题,只要所有数据之间保持联动,改了一处,对应依赖这处的数据自动更新,那就不会发生数据不一致的问题。

Redux 和 Mobx 都是一个特定时期的产物,不要因为某个工具或者技术炫酷或者热门而去用它,要根据自己的工作需要去选择工具和技术

📜 Redux 三大原则

  1. 单一数据源

  2. 状态是只读的

  3. 状态修改均有纯函数完成

redux-flow

相比 Flux 可以看出,Redux 没有 Dispatcher,Store 是单一的,Store 通过 Reducers 纯函数获取新的 State,使得状态的修改变得简单、纯粹、可测试,并且可以追踪每次变化,实现了时间旅行

🗜 middleware

It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer.

它提供了一个分类处理 action 的机会。在 middleware 中,你可以检阅每一个流过的 action,挑选出特定类型的 action 进行相应操作,给你一次改变 action 的机会

3.1 Redux 为什么需要 middleware

middleware 大部分都是用来处理异步,因为 Redux 内部是 state = reducer(state, action) 调用,所以 Redux 的 reducer 必须要是一个纯函数,不能有任何的副作用,以至 Redux 必须引入了 middleware,在 action 没有到 reducer 的时候就拦截 action 并进行副作用的操作

也正因为 reducer 是纯函数,我们通过 middleware 进行的异步操作和在一些大项目中对 action 的特殊需求的一些处理,才可以很好的进行组织管理

3.2 流程控制

由于 middleware 中的操作最终都需要 dispatch 一个 plain object,不同 middleware 中也需要 dispatch 进行跳转,所以导致 dispatch 处的逻辑难以抽象过程,除非传入 dispatch 参数,以至于 middleware 内部逻辑是一种通过 dispatch 的流程控制

🐦 高阶 reducer

higher-order-reducer:: reducer => reducer

高阶 reducer 就是指将 reducer 作为参数或者返回值的函数

combineReducers 其实就是一个高阶 reducer,因为 combineReducers 就是将一个 reducer 对象作为参数,最后返回顶层的 reducer

  • reducer 的复用:

    我们两个组件,它们的功能是相同的,都是 LOAD_DATA,我们能不能让它们共用一个 reducer?

    不能,因为当一个组件出发 action 时,另一个组件的状态也会改变

    所以在一个应用中,不同模块间的 actionType 必须是全局唯一的

    我们可以用增加前缀的方法解决:

    1const generateReducer = (prefix, state) => {
    2 const LOAD_DATA = prefix + 'LOAD_DATA'
    3 const defaultStaet = {}
    4
    5 return (state = defaultStaet, action) => {
    6 switch (action.type) {
    7 case LOAD_DATA:
    8 return {
    9 ...state,
    10 data: action.payload,
    11 }
    12 default:
    13 return state
    14 }
    15 }
    16}
  • reducer 增强

    redux-undo:

    1const undoable = reducer => {
    2 const defaultState = {
    3 past: [],
    4 current: reducer(undefined, {}),
    5 future: [],
    6 }
    7
    8 return (state = defaultState, action) => {
    9 const { past, current, future } = state
    10
    11 switch (action.type) {
    12 case '@@redux-undo/UNDO':
    13 const previous = past[past.length - 1]
    14 const newPast = past.slice(0, past.length - 1)
    15
    16 return {
    17 past: newPast,
    18 current: previous,
    19 future: [current, ...future],
    20 }
    21 case '@@redux-undo/REDO':
    22 const next = future[0]
    23 const newFuture = future.slice(1)
    24
    25 return {
    26 past: [...past, current],
    27 current: next,
    28 future: newFuture,
    29 }
    30 default:
    31 const newCurrent = reducer(current, action)
    32
    33 return {
    34 past: [...past, current],
    35 current: newCurrent,
    36 future: [],
    37 }
    38 }
    39 }
    40}

📝 源码分析

5.1 createStore

createStore 其实就把 reducer 包了一层,根本没有 store,只是暴露出几个接口,方便操作,并用发布订阅模式实现了 state 改变时执行订阅的函数的功能,然后在一开始实现 enhancer,供 applyMiddleware 使用

reducer、preloadedState/initialState 都得自己写,Redux 为我们干的很少

1const createStore = (reducer, preloadedState, enhancer) => {
2 if (typeof enhancer !== undefined) {
3 return enhancer(createStore)(reducer, preloadedState)
4 }
5
6 let currentReducer = reducer
7 let state = preloadedState
8 let listeners = []
9
10 function getState() {
11 return state
12 }
13
14 function subscribe(listener) {
15 // 利用闭包防止多次 unsubscribe
16 let isSubscribed = true
17 listeners.push(listener)
18
19 return function unsubscribe() {
20 if (!isSubscribe) return
21
22 const index = listeners.indexOf(lintener)
23 listeners.splice(index, 1)
24 isSubscribe = false
25 }
26 }
27
28 function dispatch(action) {
29 state = currentReducer(state, action) // 确定了 reducer 中不能做异步操作
30 // 依次执行订阅
31 listenters.forEach(listener => listener())
32
33 return action
34 }
35
36 function replaceReducer(nextReducer) {
37 currentReducer = nextReducer
38
39 dispatch({ type: Symbol('REPLACE') })
40 }
41
42 // dispacth 一个没有的 action,仅用来第一次初始化 state
43 dispatch({ type: Symbol('INIT') })
44
45 return {
46 dispatch,
47 subscribe,
48 getState,
49 replaceState,
50 }
51}

5.2 combineReducers

combineReducers 使我们更方便的组织 state,它干的就是将所有的 reducers 执行,使相应的 reducer 更新相应的 state,最终得到总共的新的 state

1const combineReducers = reducers => (state, action) => (
2 Object.entries(reducers).reducer((nextState, [key, reducer]) => {
3 const previousStateForKey = state[key]
4 const nextStateForKey = reducer(previousStateForKey, action)
5
6 return nextState[key] = nextStateForKey
7 }, {})
8)

5.3 compose

compose 就是典型的函数式编程中的组合函数,用于组合多个 enhancers 成一个 enhancer

1const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)))

与本文无关,记录一个 async compose:asyncPipe

5.4 applyMiddleware

applyMiddleware 可以对特定类型的 action 进行操作

createStore 中的 dispatch 由于有对 action 是否为 plainObject 的检测,所以只能 dispatch plainObject,而中间件可以穿入其他类型的 action(redux-thunk...)

实现就是通过传入旧的 createStore 返回增强过的 store,从旧的 createStore 中提取出原来 dispatch,然后对原来的 dispatch 进行包裹(增强),检测特定类型的 action 并进行的一些操作,然后仍然创建一个 plainObject 的 action,使用原来的 dispatch 派发掉

1// 对应 enhancer(createStore)(reducer, preloadedState)
2export default function applyMiddleware(...middlewares) {
3 return createStore => (...args) => {
4 const store = createStore(...args)
5 let dispatch = () => {
6 throw new Error(
7 'Dispatching while constructing your middleware is not allowed. ' +
8 'Other middleware would not be applied to this dispatch.'
9 )
10 }
11
12 // 这里一开始传入报错的 dispatch,防止在组合 middlewares 时 dispatch
13 // 之后 dispacth 改变,根据作用域得到增强后的 dispatch
14 const middlewareAPI = {
15 getState: store.getState,
16 dispatch: (...args) => dispatch(...args)
17 }
18 const chain = middlewares.map(middleware => middleware(middlewareAPI))
19 // 组合所有 middleware(enhancer),把原来的 dispatch 传入,得到包裹(增强)的 dispatch
20 // 把这行 compose 分开写就是 dispatch = thunk(logger(timer(store.dispatch)))
21 dispatch = compose(...chain)(store.dispatch)
22
23 // 返回新的 store
24 return {
25 ...store,
26 dispatch
27 }
28 }
29}

简单的 middleware,在打印日志之前输出当前的时间戳:

1// 获取 dispatch 和 getState 方法,方便内部使用,返回接收 dispatch 的函数
2// 这里 next 就对应了上一个中间件返回出来的 dispatch
3// action 就是之后开发者写的要派发的 action
4const timerMiddleware = ({ dispatch, getState }) => next => action => {
5 console.log(`timer: ${new Date().getTime()}`)
6 next(action)
7}

对应 redux-thunk 源码:

1// 其实 thunk 就是 ({ dispatch, getState }) => next => action => ... 这个函数
2// 再包一层是为了获取额外的参数,以供一些需求使用
3function createThunkMiddleware(extraArgument) {
4 return ({ dispatch, getState }) => next => action => {
5 if (typeof action === 'function') {
6 return action(dispatch, getState, extraArgument);
7 }
8
9 // action 不是 function 就用原来的 dispatch 派发掉
10 return next(action);
11 };
12}
13
14const thunk = createThunkMiddleware();
15thunk.withExtraArgument = createThunkMiddleware;
16
17export default thunk;

ref