本文从将从Redux原理出发,一步步本身实现一个简单的Redux,主要目的是了解Redux内部之间的联系。看本文以前先要知道Redux是怎么用的,对Redux用法不会讲解太多。javascript
首先要知道的是,Redux 和 React 没有关系,Redux 能够用在任何框架中。
Redux 是一个JavaScript 状态管理器,是一种新型的前端“架构模式”。html
还有一般与redux一块儿用的一个库——react-redux, 它就是把 Redux 这种架构模式和 React.js 结合起来的一个库,就是 Redux 架构在 React.js 中的体现。前端
从组件的角度看:java
从这个流程中能够看出,Redux 的核心就是一个 观察者 模式。一旦 store 发生了变化就会通知全部的订阅者,视图(在这里是react组件)接收到通知以后会进行从新渲染。react
为了简化说明,我用和官网差很少的例子改写来做案例
官网redux demogit
新建一个文件redux.js,而后直接引入,观察控制台输出github
import { createStore } from 'redux' const defaultState = { value: 10 } // reducer处理函数 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) // 监听state的改变 store.dispatch({ type: 'INCREMENT' }) // 当前数字为:11 store.dispatch({ type: 'INCREMENT' }) // 当前数字为:12 store.dispatch({ type: 'DECREMENT' }) // 当前数字为:11 export default store
输出结果:redux
{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值服务器
import { createStore } from 'redux' // 传入reducer const store = createStore(reducer)
createStore 会返回一个对象,这个对象包含三个方法,因而咱们能够列出Redux雏形。
新建mini-redux.js
export function createStore (reducer) { const getState = () => {} const subscribe = () => {} const dispatch = () => {} return { getState, subscribe, dispatch } }
export function createStore (reducer) { let currentState = {} const getState = () => currentState return { getState } }
reducer
返回一个新状态export function createStore (reducer) { let currentState = {} const getState = () => currentState const dispatch = (action) => { currentState = reducer(currentState, action) // 覆盖原来的state } return { getState, dispatch } }
怎么实现呢?咱们能够直接使用subscribe函数把你要监听的事件添加到数组, 而后执行dispatch方法的时候把listeners数组的监听函数给执行一遍。
export function createStore (reducer) { let currentState = {} let currentListeners = [] // 监听函数,可添加多个 const getState = () => currentState const subscribe = (listener) => { currentListeners.push(listener) } const dispatch = (action) => { currentState = reducer(currentState, action) // 覆盖原来的state currentListeners.forEach(listener => listener()) } return { getState, subscribe, dispatch } }
翻开一开始咱们那个Redux例子,其实就是把store.getState()
添加进来,dispatch派发一个action后,reducer执行返回新的state,并执行了监听函数store.getState()
,state的值就发生变化了。
function listener () { const current = store.getState() console.log(`当前数字为:${current.value}`) } store.subscribe(listener) // 监听state的改变
上述代码,跟React依然没有关系,只是纯属Redux例子。但想想当咱们把Redux和React一块儿用的时候,还会多作这么一步。
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会先自动执行一次,继续改代码
export function createStore (reducer) { let currentState = {} let currentListeners = [] // 监听器,可监听多个事件 const getState = () => currentState const subscribe = (listener) => { currentListeners.push(listener) } const dispatch = (action) => { currentState = reducer(currentState, action) // 覆盖原来的state currentListeners.forEach(listener => listener()) } // 尽可能写得复杂,使不会与咱们自定义的action有重复可能 dispatch({ type: '@@mini-redux/~GSDG4%FDG#*&' }) return { getState, subscribe, dispatch } }
写到这里,咱们把引入的redux替换咱们写的文件
import { createStore } from './mini-redux'
当咱们执行的时候,发现结果并不如咱们所愿:
{} {type: "@@mini-redux/~GSDG4%FDG#*&"} 一开始数字为:undefined {} {type: "INCREMENT"} 当前数字为:NaN {type: "INCREMENT"} 当前数字为:NaN {value: NaN} {type: "DECREMENT"} 当前数字为:NaN
这个怎么回事呢?由于咱们写的redux一开始就给state赋值为{},在事实state初始值是由外部传入的,一般咱们本身写的时候会设置默认值
const defaultState = { value: 10 } function reducer (state = defaultState, action) { switch (action.type) { // ... default: return state } }
但在咱们Redux实现中却把它手动置为空对象,在这里咱们暂时解决方法就是不给它赋值,让它为undefined,这样reducer的默认参数就会生效。redux初始化第一次dispatch时,就会让它自动赋值为reducer传入的第一个参数state默认值(ES6函数默认赋值),因此修改以下:
export function createStore (reducer) { let currentState let currentListeners = [] // 监听器,可监听多个事件 const getState = () => currentState const subscribe = (listener) => { currentListeners.push(listener) } const dispatch = (action) => { currentState = reducer(currentState, action) // 覆盖原来的state currentListeners.forEach(listener => listener()) } dispatch({ type: '@@mini-redux/~GSDG4%FDG#*&' }) return { getState, subscribe, dispatch } }
这个mini-redux.js,咱们就能够实现跟原来的redux彻底同样的输出效果了。
接下来咱们继续补充知识点
createStore(reducer, [preloadedState], enhancer)
第二个参数 [preloadedState] (any)
是可选的: initial state
第三个参数enhancer(function)
也是可选的:用于添加中间件的
一般状况下,经过 preloadedState 指定的 state 优先级要高于经过 reducer 指定的 state。这种机制的存在容许咱们在 reducer 能够经过指明默认参数来指定初始数据,并且还为经过服务端或者其它机制注入数据到 store 中提供了可能。
第三个参数咱们下篇会说,先继续完善一下代码,咱们须要对第二个和第三个可选参数进行判断。
export function createStore (reducer, preloadedState, enhancer) { // 当第二个参数没有传preloadedState,而直接传function的话,就会直接把这个function当成enhancer if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } // 当第三个参数传了但不是function也会报错 if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } return enhancer(createStore)(reducer, preloadedState) } // reducer必须为函数 if (typeof reducer !== 'function') { throw new Error('Expected the reducer to be a function.') } let currentState = preloadedState // 第二个参数没传默认就是undefined赋给currentState let currentListeners = [] // 监听器,可监听多个事件 // ... }
关于第三个参数判断为何返回return enhancer(createStore)(reducer, preloadedState)
咱们下篇会说,这篇先忽略。
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) // 经过filter过滤,执行的时候将以前自己已经添加进数组的事件名移除数组 return () => { currentListeners = currentListeners.filter(l => l !== listener); } } // ... }
也能够经过找数组下标的方式移除listener
const subscribe = (listener) => { if (typeof listener !== 'function') { throw new Error('Expected listener to be a function.') } currentListeners.push(listener) // 经过filter过滤,执行的时候将以前自己已经添加进数组的事件名移除数组 return () => { let index = currentListeners.indexOf(listener) currentListeners.splice(index, 1) } }
移除listener实际就是取消订阅,使用方式以下:
let unsubscribe = store.subscribe(() => console.log(store.getState()) ); unsubscribe(); // 取消监听
export function createStore (reducer, preloadedState, enhancer) { // ... let isDispatching = false const dispatch = (action) => { // 用于判断action是否为一个普通对象 if (!isPlainObject(action)) { throw new Error('Actions must be plain objects. ') } // 防止屡次dispatch请求同时改状态,必定是前面的dispatch结束以后,才dispatch下一个 if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true currentState = reducer(currentState, action) // 覆盖原来的state } finally { isDispatching = false } currentListeners.forEach(listener => listener()) return action } } // 用于判断一个值是否为一个普通的对象(普通对象即直接以字面量形式或调用 new Object() 所建立的对象) 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全部代码:
export function createStore (reducer, preloadedState, enhancer) { // 当第二个参数没有传preloadedState,而直接传function的话,就会直接把这个function当成enhancer if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } // 当第三个参数传了但不是function也会报错 if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } return enhancer(createStore)(reducer, preloadedState) } // reducer必须为函数 if (typeof reducer !== 'function') { throw new Error('Expected the reducer to be a function.') } let currentState = preloadedState // 第二个参数没传默认就是undefined赋给currentState 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) // 经过filter过滤,执行的时候将以前自己已经添加进数组的事件名移除数组 return () => { currentListeners = currentListeners.filter(l => l !== listener); } } const dispatch = (action) => { // 用于判断action是否为一个普通对象 if (!isPlainObject(action)) { throw new Error('Actions must be plain objects. ') } // 防止屡次dispatch请求同时改状态,必定是前面的dispatch结束以后,才dispatch下一个 if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true currentState = reducer(currentState, action) // 覆盖原来的state } finally { isDispatching = false } currentListeners.forEach(listener => listener()) return action } dispatch({ type: '@@mini-redux/~GSDG4%FDG#*&' }) return { getState, subscribe, dispatch } } // 用于判断一个值是否为一个普通的对象(普通对象即直接以字面量形式或调用 new Object() 所建立的对象) 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 }
参考资料: