本文从将从 Redux 原理出发,一步步自己实现一个简单的 Redux,主要目的是了解 Redux 内部之间的联系。看本文之前先要知道 Redux 是怎么用的,对 Redux 用法不会讲解太多。
Redux 介绍 首先要知道的是,Redux 和 React 没有关系,Redux 可以用在任何框架中。 Redux 是一个 JavaScript 状态管理器,是一种新型的前端“架构模式”。
还有通常与 redux 一起用的一个库——react-redux, 它就是把 Redux 这种架构模式和 React.js 结合起来的一个库,就是 Redux 架构在 React.js 中的体现。
设计思想 Web 应用是一个状态机,视图与状态是一一对应的。 所有的状态,保存在一个对象里面。 何时使用 Redux 用户的使用方式复杂 不同身份的用户有不同的使用方式(比如普通用户和管理员) 多个用户之间可以协作 与服务器大量交互,或者使用了 WebSocket View 要从多个来源获取数据 从组件的角度看:
某个组件的状态,需要共享 某个状态需要在任何地方都可以拿到 一个组件需要改变全局状态 一个组件需要改变另一个组件的状态 Redux 工作流程 
Redux 将整个应用状态(state)存储到一个地方(通常我们称其为 store) 当我们需要修改状态时,必须派发(dispatch)一个 action( action 是一个带有 type 字段的对象) 专门的状态处理函数 reducer 接收旧的 state 和 action ,并会返回一个新的 state 通过 subscribe 设置订阅,每次派发动作时,通知所有的订阅者。 从这个流程中可以看出,Redux 的核心就是一个 观察者 模式。一旦 store 发生了变化就会通知所有的订阅者,视图(在这里是 react 组件)接收到通知之后会进行重新渲染。
Redux 案例 为了简化说明,我用和官网差不多的例子改写来作案例官网 redux demo 
新建一个文件 redux.js,然后直接引入,观察控制台输出
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 import  { createStore } from  'redux' const  defaultState = {  value: 10 , } function  reducer (state = defaultState, action )  {  console .log(state, action)   switch  (action.type) {     case  'INCREMENT' :       return  {         ...state,         value: state.value + 1 ,       }     case  'DECREMENT' :       return  {         ...state,         value: state.value - 1 ,       }     default :       return  state   } } const  store = createStore(reducer)const  init = store.getState()console .log(`一开始数字为:${init.value} ` )function  listener ( )  {  const  current = store.getState()   console .log(`当前数字为:${current.value} ` ) } store.subscribe(listener)  store.dispatch({ type : 'INCREMENT'  }) store.dispatch({ type : 'INCREMENT'  }) store.dispatch({ type : 'DECREMENT'  }) export  default  store
输出结果:
1 2 3 4 5 6 7 8 {value: 10} {type: "@@redux/INIT1.a.7.g.7.t"} 一开始数字为:10 {value: 10} {type: "INCREMENT"} 当前数字为:11 {value: 11} {type: "INCREMENT"} 当前数字为:12 {value: 12} {type: "DECREMENT"} 当前数字为:11 
所有对数据的操作必须通过 dispatch 函数,它接受一个参数 action,action 是一个普通的 JavaScript 对象,action 必须包含一个type字段,告诉它要修改什么,只有它允许才能修改。
在每次调用进来 reducer 函数我们都打印了 state 和 action,我们手动通过 store.dispatch 方法派发了三次 action,但你会发现输出了四次.这是因为 Redux 内部初始化就自动执行了一次 dispatch 方法,可以看到第一次执行它的 type 对我们数据来说是没有影响的(因为 type 取值@@redux/INIT1.a.7.g.7.t,我们自己 redux 的数据 type 不会取名成这个样子,所以不会跟它重复),即默认输出 state 值
动手实现一个 Redux 首先要手动实现一个 Redux 之前,先看看有上述代码涉及到 Redux 什么方法,首先引入了 1 2 3 import { createStore } from 'redux' // 传入reducer const store = createStore(reducer) 
createStore 会返回一个对象,这个对象包含三个方法,于是我们可以列出 Redux 雏形。
新建 mini-redux.js
1 2 3 4 5 6 7 8 9 10 11 12 export function createStore (reducer) {     const getState = () => {}     const subscribe = () => {}     const dispatch = () => {}     return {         getState,         subscribe,         dispatch     } } 
store.getState()用于获取 state 数据,其实就是简单地把 state 参数返回。于是 1 2 3 4 5 6 7 8 export  function  createStore (reducer )  {  let  currentState = {}   const  getState = ()  =>  currentState   return  {     getState,   } } 
dispatch 方法会接收一个 action,执行会调用 reducer 返回一个新状态 1 2 3 4 5 6 7 8 9 10 11 export  function  createStore (reducer )  {  let  currentState = {}   const  getState = ()  =>  currentState   const  dispatch = action  =>  {     currentState = reducer(currentState, action)    }   return  {     getState,     dispatch,   } } 
通过 subscribe 设置监听函数(设置订阅),一旦 state 发生变化,就自动执行这个函数(通知所有的订阅者)。 怎么实现呢?我们可以直接使用 subscribe 函数把你要监听的事件添加到数组, 然后执行 dispatch 方法的时候把 listeners 数组的监听函数给执行一遍。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 export  function  createStore (reducer )  {  let  currentState = {}   let  currentListeners = []    const  getState = ()  =>  currentState   const  subscribe = listener  =>  {     currentListeners.push(listener)   }   const  dispatch = action  =>  {     currentState = reducer(currentState, action)      currentListeners.forEach(listener  =>  listener())   }   return  {     getState,     subscribe,     dispatch,   } } 
翻开一开始我们那个 Redux 例子,其实就是把store.getState()添加进来,dispatch 派发一个 action 后,reducer 执行返回新的 state,并执行了监听函数store.getState(),state 的值就发生变化了。
1 2 3 4 5 function  listener ( )  {  const  current = store.getState()   console .log(`当前数字为:${current.value} ` ) } store.subscribe(listener)  
上述代码,跟 React 依然没有关系,只是纯属 Redux 例子。但想一想当我们把 Redux 和 React 一起用的时候,还会多做这么一步。
1 2 3 4 5 6 7 8 9 10  constructor (props) {     super (props)     this .state = store.getState()     this .storeChange = this .storeChange.bind(this )     store.subscribe(this .storeChange) } storeChange () {     this .setState(store.getState()) } 
在 React 里面监听的方法,还要用this.setState(), 这是因为 React 中 state 的改变必须依赖于this.setState方法。所以对于 React 项目,就是组件的 render 方法或 setState 方法放入 listen(监听函数),才会实现视图的自动渲染,改变页面中的 state 值。
最后一步,注意我们上面说的,当初始化的时候,dispatch 会先自动执行一次,继续改代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 export  function  createStore (reducer )  {  let  currentState = {}   let  currentListeners = []    const  getState = ()  =>  currentState   const  subscribe = listener  =>  {     currentListeners.push(listener)   }   const  dispatch = action  =>  {     currentState = reducer(currentState, action)      currentListeners.forEach(listener  =>  listener())   }      dispatch({ type : '@@mini-redux/~GSDG4%FDG#*&'  })   return  {     getState,     subscribe,     dispatch,   } } 
写到这里,我们把引入的 redux 替换我们写的文件
1 import { createStore } from './mini-redux' 
当我们执行的时候,发现结果并不如我们所愿:
1 2 3 4 5 6 7 8 {} {type: "@@mini-redux/~GSDG4%FDG#*&"} 一开始数字为:undefined {} {type: "INCREMENT"} 当前数字为:NaN {type: "INCREMENT"} 当前数字为:NaN {value: NaN} {type: "DECREMENT"} 当前数字为:NaN 
这个怎么回事呢?因为我们写的 redux 一开始就给 state 赋值为{},在事实 state 初始值是由外部传入的,通常我们自己写的时候会设置默认值
1 2 3 4 5 6 7 8 9 10 const  defaultState = {  value: 10 , } function  reducer (state = defaultState, action )  {  switch  (action.type) {          default :       return  state   } } 
但在我们 Redux 实现中却把它手动置为空对象,在这里我们暂时解决方法就是不给它赋值,让它为 undefined,这样 reducer 的默认参数就会生效。redux 初始化第一次 dispatch 时,就会让它自动赋值为 reducer 传入的第一个参数 state 默认值(ES6 函数默认赋值),所以修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 export  function  createStore (reducer )  {  let  currentState   let  currentListeners = []    const  getState = ()  =>  currentState   const  subscribe = listener  =>  {     currentListeners.push(listener)   }   const  dispatch = action  =>  {     currentState = reducer(currentState, action)      currentListeners.forEach(listener  =>  listener())   }   dispatch({ type : '@@mini-redux/~GSDG4%FDG#*&'  })   return  {     getState,     subscribe,     dispatch,   } } 
这个 mini-redux.js,我们就可以实现跟原来的 redux 完全一样的输出效果了。
完善 Redux 接下来我们继续补充知识点
createStore 实际有三个参数,即 1 createStore(reducer, [preloadedState], enhancer) 
第二个参数 [preloadedState] (any)是可选的: initial state
第三个参数enhancer(function)也是可选的:用于添加中间件的
通常情况下,通过 preloadedState 指定的 state 优先级要高于通过 reducer 指定的 state。这种机制的存在允许我们在 reducer 可以通过指明默认参数来指定初始数据,而且还为通过服务端或者其它机制注入数据到 store 中提供了可能。
第三个参数我们下篇会说,先继续完善一下代码,我们需要对第二个和第三个可选参数进行判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 export  function  createStore (reducer, preloadedState, enhancer )  {     if  (typeof  preloadedState === 'function'  && typeof  enhancer === 'undefined' ) {     enhancer = preloadedState     preloadedState = undefined    }      if  (typeof  enhancer !== 'undefined' ) {     if  (typeof  enhancer !== 'function' ) {       throw  new  Error ('Expected the enhancer to be a function.' )     }     return  enhancer(createStore)(reducer, preloadedState)   }      if  (typeof  reducer !== 'function' ) {     throw  new  Error ('Expected the reducer to be a function.' )   }   let  currentState = preloadedState    let  currentListeners = []     } 
关于第三个参数判断为什么返回return enhancer(createStore)(reducer, preloadedState)我们下篇会说,这篇先忽略。 2. 我们实现了 store.subscribe()方法,但还是不完整的,subscribe 方法可以添加监听函数 listener,它还有返回值,返回一个移除 listener 的函数;另外我们依然要对类型进行判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 export  function  createStore (reducer, preloadedState, enhancer )  {     let  currentListeners = []    const  subscribe = listener  =>  {     if  (typeof  listener !== 'function' ) {       throw  new  Error ('Expected listener to be a function.' )     }     currentListeners.push(listener)          return  ()  =>  {       currentListeners = currentListeners.filter(l  =>  l !== listener)     }   }    } 
也可以通过找数组下标的方式移除 listener
1 2 3 4 5 6 7 8 9 10 11 const  subscribe = listener  =>  {  if  (typeof  listener !== 'function' ) {     throw  new  Error ('Expected listener to be a function.' )   }   currentListeners.push(listener)      return  ()  =>  {     let  index = currentListeners.indexOf(listener)     currentListeners.splice(index, 1 )   } } 
移除 listener 实际就是取消订阅,使用方式如下:
1 2 3 let  unsubscribe = store.subscribe(()  =>  console .log(store.getState()))unsubscribe()  
diaptch 方法执行完返回为 action,然后我们同样需要为它作判断 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 export  function  createStore (reducer, preloadedState, enhancer )  {     let  isDispatching = false    const  dispatch = action  =>  {          if  (!isPlainObject(action)) {       throw  new  Error ('Actions must be plain objects. ' )     }          if  (isDispatching) {       throw  new  Error ('Reducers may not dispatch actions.' )     }     try  {       isDispatching = true        currentState = reducer(currentState, action)      } finally  {       isDispatching = false      }     currentListeners.forEach(listener  =>  listener())     return  action   } } export  function  isPlainObject (obj )  {  if  (typeof  obj !== 'object'  || obj === null ) return  false    let  proto = obj   while  (Object .getPrototypeOf(proto) !== null ) {     proto = Object .getPrototypeOf(proto)   }   return  Object .getPrototypeOf(obj) === proto } 
isPlainObject 函数中通过 while 不断地判断 Object.getPrototypeOf(proto) !== null 并执行, 最终 proto 会指向 Object.prototype. 这时再判断 Object.getPrototypeOf(obj) === proto, 如果为 true 的话就代表 obj 是通过字面量或调用 new Object() 所创建的对象了。
保持 action 对象是简单对象的作用是方便 reducer 进行处理,不用处理其他的情况(比如 function/class 实例等)
至此,我们实现了最基本能用的 Redux 代码,下篇再继续完善 Redux 代码,最后放出基础版 Redux 所有代码:
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 66 67 68 69 70 71 72 73 74 75 export  function  createStore (reducer, preloadedState, enhancer )  {     if  (typeof  preloadedState === 'function'  && typeof  enhancer === 'undefined' ) {     enhancer = preloadedState     preloadedState = undefined    }      if  (typeof  enhancer !== 'undefined' ) {     if  (typeof  enhancer !== 'function' ) {       throw  new  Error ('Expected the enhancer to be a function.' )     }     return  enhancer(createStore)(reducer, preloadedState)   }      if  (typeof  reducer !== 'function' ) {     throw  new  Error ('Expected the reducer to be a function.' )   }   let  currentState = preloadedState    let  currentListeners = []    let  isDispatching = false    const  getState = ()  =>  currentState   const  subscribe = listener  =>  {     if  (typeof  listener !== 'function' ) {       throw  new  Error ('Expected listener to be a function.' )     }     currentListeners.push(listener)          return  ()  =>  {       currentListeners = currentListeners.filter(l  =>  l !== listener)     }   }   const  dispatch = action  =>  {          if  (!isPlainObject(action)) {       throw  new  Error ('Actions must be plain objects. ' )     }          if  (isDispatching) {       throw  new  Error ('Reducers may not dispatch actions.' )     }     try  {       isDispatching = true        currentState = reducer(currentState, action)      } finally  {       isDispatching = false      }     currentListeners.forEach(listener  =>  listener())     return  action   }   dispatch({ type : '@@mini-redux/~GSDG4%FDG#*&'  })   return  {     getState,     subscribe,     dispatch,   } } export  function  isPlainObject (obj )  {  if  (typeof  obj !== 'object'  || obj === null ) return  false    let  proto = obj   while  (Object .getPrototypeOf(proto) !== null ) {     proto = Object .getPrototypeOf(proto)   }   return  Object .getPrototypeOf(obj) === proto } 
参考资料:
Redux 入门教程(一):基本用法