Redux 原理解析

274 阅读3分钟

首先简单的说明一下redux中的几个名词的作用。

一个灯有 亮 或 不亮 两个状态( state),提供了一个开关用于控制灯亮或不亮 (reducer), 往下按是亮、往上按是不亮(由action.type定义,也可以上下反过来),接下来人就可以选择往上按还是往下按(dispatch) 控制灯的亮或不亮

state: 可能会发生改变的值 (状态,也就是变量, 不变的那叫常量)

reducer :用于修改state的状态

action: 约定是一个包含type与paylaod的一个对象。

dispatch: 传入action 触发reducer 并根据action.type 决定执行reducer中的哪个操作

执行流程: 如:dispatch(action) = dispatch({type:'', payload:{} }) -> 执行 reducer() -> 修改state。

首先上例子

有两个reducer, countReducer 的默认state为0, sumReducer的默认state为1

并且countReducer 的action.type与sumReducer的action.type都有‘INCREMENT’ 或'DECREMENT',

使用combineReducers 组合两个reducer并将返回的函数combinefn传入createStore中执行返回需要的store.

reducer 的 state 是在 参数上给的默认值

 import {  createStore, combineReducers } from 'redux';
 ​
 function countReducer(state = 0, action) {
     switch (action.type) {
         case 'INCREMENT':
           return state + 1;
         case 'DECREMENT':
           return state - 1;
         default:
           return state;
     }
 }
 ​
 function sumReducer(state = 1, action) {
     switch (action.type) {
         case 'INCREMENT':
           return state + 1;
         case 'DECREMENT':
           return state - 1;
         default:
           return state;
     }
 }
 ​
 const combinefn = combineReducers({ countReducer, sumReducer})
 const  store =  createStore(combinefn);
 ​
 export default store

combineReducers

源码如下:

 export default function combineReducers(reducers) {
   const reducerKeys = Object.keys(reducers)
   const finalReducers = {}
   for (let i = 0; i < reducerKeys.length; i++) {
     const key = reducerKeys[i]
     if (typeof reducers[key] === 'function') {
       finalReducers[key] = reducers[key]
     }
   }
   const finalReducerKeys = Object.keys(finalReducers)
     return function combination(
         state= {},
         action
         ) {
         let hasChanged = false
         const nextState = {}
         for (let i = 0; i < finalReducerKeys.length; i++) {
             const key = finalReducerKeys[i]
             const reducer = finalReducers[key]
             const previousStateForKey = state[key]
             const nextStateForKey = reducer(previousStateForKey, action)
             if (typeof nextStateForKey === 'undefined') {
                 const actionType = action && action.type
                 throw new Error('more error')
             }
             nextState[key] = nextStateForKey
             hasChanged = hasChanged || nextStateForKey !== previousStateForKey
         }
         hasChanged =
             hasChanged || finalReducerKeys.length !== Object.keys(state).length
         return hasChanged ? nextState : state
     }
 }reducers参数: 在这个例子中此时为 { countReducer, sumReducer}。

  1. 首先排除reducers对象中值不是函数的项 , 并赋值给 finalReducers
  2. 然后取出 reducers 所有的键保存至finalReducerKeys
  3. 最后返回combination函数,此函数作为createStore的参数

combination就是一个reducer,因为他就是一个用于修改state状态的方法,为了跟初始时传入的reducer混淆,以下都使用combination 代表这个 reducer

在该例中此时:
finalReducers = {
countReducer: countReducer,
sumReducer: sumReducer
}
finalReducerKeys = ['countReducer', 'sumReducer']

finalReducersfinalReducerKeys 会在combination中用到 (下面会再次提及)

combination 当dispatch({type: ''}) 时触发,遍历所有的reducer, 执行所有reducer中逻辑满足 type 的 修改state的语句。比如:

type为INCREMENT 就执行return state + 1; 修改状态

 dispatch({type: 'INCREMENT'}) 
 function sumReducer(state = 1, action) {
     switch (action.type) {
         case 'INCREMENT':
           return state + 1;
     }
 }

createStore

  1. reducer (Function): 使用了 combineReducers时,对应就是combination 函数,没使用对应的就是传入的单个reducer 函数
  2. [preloadedState] (any): 初始时的 state。
    如果使用 [combineReducers](https://link.zhihu.com/?target=https%3A//www.redux.org.cn/docs/api/combineReducers.html) 创建 reducer,preloadedState最终作为第一次执行combination时的参数传入。 默认不传时也就是combination的state ={},所以它必须是一个普通对象,并且与combineReducers传入的对象 keys 保持同样的结构, 如果不保持,会抛出错误,(期望参数与combineReducers传入的对象,keys保持一致)
    在同构应用中,你可以决定是否把服务端传来的 state 水合)(hydrate)后传给它,或者从之前保存的用户会话中恢复一个传给它。。
  3. enhancer (Function): Store enhancer 是一个组合 store creator 的高阶函数,返回一个新的强化过的 store creator。一个简单的作用就是支持的中间件。
    具体作用参照 createStore 的第三个参数还有这个作用 , Redux源码解读拾遗,createStore的第三个参数

这里将 reducer 赋值给了 currentReducer, 也就是将combination 赋值给了currentReducer

 // 省略了若干代码
 export default function createStore(reducer, preloadedState, enhancer) {
     // 省略参数校验和替换
     // 当前的 reducer 函数, 在这个案例 初次也就是`combination` 函数
     let currentReducer = reducer
     // 当前state
     let currentState = preloadedState
     // 当前的监听数组函数
     let currentListeners = []
     // 下一个监听数组函数
     let nextListeners = currentListeners
     // 是否正在dispatch中
     let isDispatching = false
     function ensureCanMutateNextListeners() {
         if (nextListeners === currentListeners) {
         nextListeners = currentListeners.slice()
         }
     }
     function getState() {
         return currentState
     }
     function subscribe(listener) {}
     function dispatch(action) {}
     function replaceReducer(nextReducer) {}
     function observable() {}
     dispatch({ type: ActionTypes.INIT })
     return {
         dispatch,
         subscribe,
         getState,
         replaceReducer,
         [$$observable]: observable
     }
 }

也就是说最终 store 为一个对象并包括如下几个属性:

 {
     dispatch,
     subscribe,
     getState,
     replaceReducer,
     [$$observable]: observable
 }

接下来看这几个属性对应的函数的作用

createStore 中的一些函数

dispatch

作用: 通过dispatch更新数据,

执行createStore时内部第一次执行 dispatch({ type: ActionTypes.INIT }) 执行 currentReducer 初始化state, (currentReducer 对应的就是上面提到的combineReducers返回的函数combination

**注意:**除了执行 currentReducer 计算state, 还遍历执行了listeners也就是currentListenerslistenerssubscribe订阅的函数,也就是当状态改变,通知订阅者更新。 (下面会继续提及listeners, 先记住)

源码如下:

再次提醒currentReducer 就是 上面提及的combination函数

 //  currentState = createStore的第二个参数,而且不要是函数类型的,不然会被认为是`enhancer` 
 function dispatch(action: A) {
     try {
       isDispatching = true
                     // currentReducer 就是 combination
       currentState = currentReducer(currentState, action)
     } finally {
       isDispatching = false
     }
     // 执行所有状态更新后需要执行的函数
     const listeners = (currentListeners = nextListeners)
     for (let i = 0; i < listeners.length; i++) {
       const listener = listeners[i]
       listener()
     }
 ​
     return action
   }

再次贴上combination代码:

 // 前面在combineReducers中提及了,此时这两个对象的值,
 // finalReducers = {
 //    countReducer: countReducer,
 //    sumReducer: sumReducer
 // }
 // finalReducerKeys = ['countReducer', 'sumReducer'] 
 ​
 return function combination(
     state= {},
     action
     ) {
     let hasChanged = false
     const nextState = {}
     for (let i = 0; i < finalReducerKeys.length; i++) {
         const key = finalReducerKeys[i]
         const reducer = finalReducers[key]
         const previousStateForKey = state[key]
         const nextStateForKey = reducer(previousStateForKey, action)
         if (typeof nextStateForKey === 'undefined') {
             const actionType = action && action.type
             throw new Error('more error')
         }
         nextState[key] = nextStateForKey
         hasChanged = hasChanged || nextStateForKey !== previousStateForKey
     }
     hasChanged =
         hasChanged || finalReducerKeys.length !== Object.keys(state).length
     return hasChanged ? nextState : state
 }

每次执行dispatch时,都会执行该函数,并且传入上一次state本次dispatch的action,遍历finalReducerKeys, 循环执行所有的reducer。

也就是说 不同的reducer中如果action.type一样,那么都会执行,可能会发生预期之外的事情 如:countReducer 与 sumReducer, 中action 都有 INCREMENT,那么他们的状态都会变,细思极恐,所以避免使用同样的action.type, 如下例子:

 dispatch({type: 'INCREMENT'})
 function countReducer(state = 0, action) {
     switch (action.type) {
         case 'INCREMENT':
           return state + 1;
     }
 }
 ​
 function sumReducer(state = 1, action) {
     switch (action.type) {
         case 'INCREMENT':
           return state + 1;
     }
 }

也就是说第一次内部执行dispatch({ type: ActionTypes.INIT }) 后,整个状态树的值就是如下所示内容。

 currentState = {
     countReducer: 0,
     sumReducer: 1
 }

getState

作用:通过store.getState() 获取值

源码如下:返回currentState

   function getState(){
     return currentState
   }

阶段总结

目前为止,就已经定义好状态树且可以修改状态树了,并且dispatch函数中会遍历listeners 执行。前面也提及到了listeners 中的函数 是由 subscribe 订阅而来,接下来就继续看下 subscribe 的实现

subscribe

react中使用方式如下:

store.subscribe(render) 将render放入subscribe中, 也就是说,每当dispatch 更改状态,都会使整个App进行重新渲染,这也符合react的执行机制。

 import React from 'react';
 import ReactDOM from 'react-dom';
 import App from './App';
 import store from './redux/index';
 const render = () => ReactDOM.render(
     <App  store={ store } />,
   document.getElementById('root')
 );
 ​
 render();
 const unsubscribe = store.subscribe(render)
 // unsubscribe()  执行这句就会取消订阅

部分源码: 就是将函数加入到数组中,并且返回一个unsubscribe,作用是可从数组中删除刚加入的函数, 在dispatch时就遍历执行currentListeners数组中的函数。

   let currentListeners = []
   let nextListeners = currentListeners;
   
   function ensureCanMutateNextListeners() {
     if (nextListeners === currentListeners) {
       nextListeners = currentListeners.slice()
     }
   }
 ​
 function subscribe(listener: () => void) {
     
     let isSubscribed = true
     ensureCanMutateNextListeners()
     nextListeners.push(listener)
     return function unsubscribe() {
       isSubscribed = false
       ensureCanMutateNextListeners()
       const index = nextListeners.indexOf(listener)
       nextListeners.splice(index, 1)
       currentListeners = null
     }
   }
 ​

replaceReducer

由一般情况下会在初始化的时候使用combineReducers组合所有的reducer 初始化整个 state 树,

但使用路由时我们希望当页面加载后,再组合这个页面需要的 reducer 与state,

这时候就需要用到combineReducers 将新的reducer与已存在的reducer组合到一起,

前面提及combineReducers 返回的是 combination 函数,

并且在createStore 中将combination 赋值给了 currentReducer (忘记了可以在看下前面分析createStore的解释), 并在dispatch中使用的就是 currentReducer 重新计算state,

combineReducers 合并新的reducer之后,依旧返回的是combination 函数

所以现在我们就需要使用replaceReduce 把 旧的 currentReducer 替换掉,并执行一次dispatch, 重新计算整个state树。

到此state树中就加入了新页面的reducer和state了

   function replaceReduce(nextReducer) {
     currentReducer = nextReducer
     dispatch({ type: ActionTypes.REPLACE } as A)
     return store
   }

这里不提供示例, 详细示例使用请参考: Redux store 的动态注入reducer

redux其他的一些函数

bindactioncreators

此函数就是一个工具函数。具体功能 推荐看这篇内容: React+Redux之bindactioncreators的用法

compose

applyMiddleware

这两个函数用于中间件,单独放下一篇文章描述

参考链接

redux官网文档

Redux store 的动态注入reducer

React+Redux之bindactioncreators的用法

川哥 - 学习 redux 源码整体架构,深入理解 redux 及其中间件原理