Redux可是一个赫赫有名的库,很多中央都在用,我也用了几年了,明天这篇文章就是本人来实现一个Redux,以便于深刻了解他的原理。咱们还是老套路,从根本的用法动手,而后本人实现一个Redux来代替源码的NPM包,然而性能放弃不变。本文只会实现Redux的外围库,跟其余库的配合应用,比方React-Redux筹备前面独自写一篇文章来讲。有时候咱们过于关注应用,只记住了各种应用形式,反而疏忽了他们的外围原理,然而如果咱们想真正的进步技术,最好还是一个一个搞清楚,比方Redux和React-Redux看起来很像,然而他们的核心理念和关注点是不同的,Redux其实只是一个单纯状态治理库,没有任何界面相干的货色,React-Redux关注的是怎么将Redux跟React联合起来,用到了一些React的API。

基本概念

Redux的概念有很多文章都讲过,想必大家都看过很多了,我这里不再开展,只是简略提一下。Redux基本概念次要有以下几个:

Store

人如其名,Store就是一个仓库,它存储了所有的状态(State),还提供了一些操作他的API,咱们后续的操作其实都是在操作这个仓库。如果咱们的仓库是用来放牛奶的,初始状况下,咱们的仓库外面一箱牛奶都没有,那Store的状态(State)就是:

{    milk: 0}

Actions

一个Action就是一个动作,这个动作的目标是更改Store中的某个状态,Store还是下面的那个仓库,当初我想往仓库放一箱牛奶,那"我想往仓库放一箱牛奶"就是一个Action,代码就是这样:

{  type: "PUT_MILK",  count: 1}

Reducers

后面"我想往仓库放一箱牛奶"只是想了,还没操作,具体操作要靠Reducer,Reducer就是依据接管的Action来扭转Store中的状态,比方我接管了一个PUT_MILK,同时数量count是1,那放进去的后果就是milk减少了1,从0变成了1,代码就是这样:

const initState = {  milk: 0}function reducer(state = initState, action) {  switch (action.type) {    case 'PUT_MILK':      return {...state, milk: state.milk + action.count}    default:      return state  }}

能够看到Redux自身就是一个单纯的状态机,Store寄存了所有的状态,Action是一个扭转状态的告诉,Reducer接管到告诉就更改Store中对应的状态。

简略例子

上面咱们来看一个简略的例子,蕴含了后面提到的Store,Action和Reducer这几个概念:

import { createStore } from 'redux';const initState = {  milk: 0};function reducer(state = initState, action) {  switch (action.type) {    case 'PUT_MILK':      return {...state, milk: state.milk + action.count};    case 'TAKE_MILK':      return {...state, milk: state.milk - action.count};    default:      return state;  }}let store = createStore(reducer);// subscribe其实就是订阅store的变动,一旦store产生了变动,传入的回调函数就会被调用// 如果是联合页面更新,更新的操作就是在这里执行store.subscribe(() => console.log(store.getState()));// 将action收回去要用dispatchstore.dispatch({ type: 'PUT_MILK' });    // milk: 1store.dispatch({ type: 'PUT_MILK' });    // milk: 2store.dispatch({ type: 'TAKE_MILK' });   // milk: 1

本人实现

后面咱们那个例子尽管短小,然而曾经蕴含了Redux的外围性能了,所以咱们手写的第一个指标就是替换这个例子中的Redux。要替换这个Redux,咱们得先晓得他外面都有什么货色,认真一看,咱们如同只用到了他的一个API:

createStore:这个API承受reducer办法作为参数,返回一个store,次要性能都在这个store上。

看看store上咱们都用到了啥:

store.subscribe: 订阅state的变动,当state变动的时候执行回调,能够有多个subscribe,外面的回调会顺次执行。

store.dispatch: 收回action的办法,每次dispatch action都会执行reducer生成新的state,而后执行subscribe注册的回调。

store.getState:一个简略的办法,返回以后的state

看到subscribe注册回调,dispatch触发回调,想到了什么,这不就是公布订阅模式吗?

function createStore() {  let state;              // state记录所有状态  let listeners = [];     // 保留所有注册的回调  function subscribe(callback) {    listeners.push(callback);       // subscribe就是将回调保留下来  }  // dispatch就是将所有的回调拿进去顺次执行就行  function dispatch() {    for (let i = 0; i < listeners.length; i++) {      const listener = listeners[i];      listener();    }  }  // getState间接返回state  function getState() {    return state;  }  // store包装一下后面的办法间接返回  const store = {    subscribe,    dispatch,    getState  }  return store;}

参考 前端进阶面试题具体解答

上述代码是不是很简略嘛,Redux外围也是一个公布订阅模式,就是这么简略!等等,如同漏了啥,reducer呢?reducer的作用是在公布事件的时候扭转state,所以咱们的dispatch在执行回调前应该先执行reducer,用reducer的返回值从新给state赋值,dispatch改写如下:

function dispatch(action) {  state = reducer(state, action);  for (let i = 0; i < listeners.length; i++) {    const listener = listeners[i];    listener();  }}

到这里,后面例子用到的所有API咱们都本人实现了,咱们用本人的Redux来替换下官网的Redux试试:

// import { createStore } from 'redux';import { createStore } from './myRedux';

能够看到输入后果是一样的,阐明咱们本人写的Redux没有问题:

理解了Redux的外围原理,咱们再去看他的源码应该就没有问题了

最初咱们再来梳理下Redux的外围流程,留神单纯的Redux只是个状态机,是没有View层的哦。

除了这个外围逻辑外,Redux外面还有些API也很有意思,咱们也来手写下。

手写combineReducers

combineReducers也是应用十分宽泛的API,当咱们利用越来越简单,如果将所有逻辑都写在一个reducer外面,最终这个文件可能会有成千上万行,所以Redux提供了combineReducers,能够让咱们为不同的模块写本人的reducer,最终将他们组合起来。比方咱们最开始那个牛奶仓库,因为咱们的业务倒退很好,咱们又减少了一个放大米的仓库,咱们能够为这两个仓库创立本人的reducer,而后将他们组合起来,应用办法如下:

import { createStore, combineReducers } from 'redux';const initMilkState = {  milk: 0};function milkReducer(state = initMilkState, action) {  switch (action.type) {    case 'PUT_MILK':      return {...state, milk: state.milk + action.count};    case 'TAKE_MILK':      return {...state, milk: state.milk - action.count};    default:      return state;  }}const initRiceState = {  rice: 0};function riceReducer(state = initRiceState, action) {  switch (action.type) {    case 'PUT_RICE':      return {...state, rice: state.rice + action.count};    case 'TAKE_RICE':      return {...state, rice: state.rice - action.count};    default:      return state;  }}// 应用combineReducers组合两个reducerconst reducer = combineReducers({milkState: milkReducer, riceState: riceReducer});let store = createStore(reducer);store.subscribe(() => console.log(store.getState()));// 操作的actionstore.dispatch({ type: 'PUT_MILK', count: 1 });    // milk: 1store.dispatch({ type: 'PUT_MILK', count: 1 });    // milk: 2store.dispatch({ type: 'TAKE_MILK', count: 1 });   // milk: 1// 操作大米的actionstore.dispatch({ type: 'PUT_RICE', count: 1 });    // rice: 1store.dispatch({ type: 'PUT_RICE', count: 1 });    // rice: 2store.dispatch({ type: 'TAKE_RICE', count: 1 });   // rice: 1

下面代码咱们将大的state分成了两个小的milkStatericeState,最终运行后果如下:

晓得了用法,咱们尝试本人来写下呢!要手写combineReducers,咱们先来剖析下他干了啥,首先它的返回值是一个reducer,这个reducer同样会作为createStore的参数传进去,阐明这个返回值是一个跟咱们之前一般reducer构造一样的函数。这个函数同样接管stateaction而后返回新的state,只是这个新的state要合乎combineReducers参数的数据结构。咱们尝试来写下:

function combineReducers(reducerMap) {  const reducerKeys = Object.keys(reducerMap);    // 先把参数外面所有的键值拿进去  // 返回值是一个一般构造的reducer函数  const reducer = (state = {}, action) => {    const newState = {};    for(let i = 0; i < reducerKeys.length; i++) {      // reducerMap外面每个键的值都是一个reducer,咱们把它拿进去运行下就能够失去对应键新的state值      // 而后将所有reducer返回的state依照参数外面的key组装好      // 最初再返回组装好的newState就行      const key = reducerKeys[i];      const currentReducer = reducerMap[key];      const prevState = state[key];      newState[key] = currentReducer(prevState, action);    }    return newState;  };  return reducer;}

手写applyMiddleware

middleware是Redux外面很重要的一个概念,Redux的生态次要靠这个API接入,比方咱们想写一个logger的中间件能够这样写(这个中间件来自于官网文档):

// logger是一个中间件,留神返回值嵌了好几层函数// 咱们前面来看看为什么这么设计function logger(store) {  return function(next) {    return function(action) {      console.group(action.type);      console.info('dispatching', action);      let result = next(action);      console.log('next state', store.getState());      console.groupEnd();      return result    }  }}// 在createStore的时候将applyMiddleware作为第二个参数传进去const store = createStore(  reducer,  applyMiddleware(logger))

能够看到上述代码为了反对中间件,createStore反对了第二个参数,这个参数官网称为enhancer,顾名思义他是一个增强器,用来加强store的能力的。官网对于enhancer的定义如下:

type StoreEnhancer = (next: StoreCreator) => StoreCreator

下面的构造的意思是说enhancer作为一个函数,他接管StoreCreator函数作为参数,同时返回的也必须是一个StoreCreator函数。留神他的返回值也是一个StoreCreator函数,也就是咱们把他的返回值拿进去继续执行应该失去跟之前的createStore一样的返回构造,也就是说咱们之前的createStore返回啥构造,他也必须返回构造,也就是这个store

{  subscribe,  dispatch,  getState}

createStore反对enhancer

依据他对于enhancer的定义,咱们来改写下本人的createStore,让他反对enhancer

function createStore(reducer, enhancer) {   // 接管第二个参数enhancer  // 先解决enhancer  // 如果enhancer存在并且是函数  // 咱们将createStore作为参数传给他  // 他应该返回一个新的createStore给我  // 我再拿这个新的createStore执行,应该失去一个store  // 间接返回这个store就行  if(enhancer && typeof enhancer === 'function'){    const newCreateStore = enhancer(createStore);    const newStore = newCreateStore(reducer);    return newStore;  }  // 如果没有enhancer或者enhancer不是函数,间接执行之前的逻辑  // 上面这些代码都是之前那版  // 省略n行代码    // .......  const store = {    subscribe,    dispatch,    getState  }  return store;}

applyMiddleware返回值是一个enhancer

后面咱们曾经有了enhancer的根本构造,applyMiddleware是作为第二个参数传给createStore的,也就是说他是一个enhancer,精确的说是applyMiddleware的返回值是一个enhancer,因为咱们传给createStore的是他的执行后果applyMiddleware()

function applyMiddleware(middleware) {  // applyMiddleware的返回值应该是一个enhancer  // 依照咱们后面说的enhancer的参数是createStore  function enhancer(createStore) {    // enhancer应该返回一个新的createStore    function newCreateStore(reducer) {      // 咱们先写个空的newCreateStore,间接返回createStore的后果      const store = createStore(reducer);      return store    }    return newCreateStore;  }  return enhancer;}

实现applyMiddleware

下面咱们曾经有了applyMiddleware的根本构造了,然而性能还没实现,要实现他的性能,咱们必须先搞清楚一个中间件到底有什么性能,还是以后面的logger中间件为例:

function logger(store) {  return function(next) {    return function(action) {      console.group(action.type);      console.info('dispatching', action);      let result = next(action);      console.log('next state', store.getState());      console.groupEnd();      return result    }  }}

这个中间件运行成果如下:

能够看到咱们let result = next(action);这行执行之后state扭转了,后面咱们说了要扭转state只能dispatch(action),所以这里的next(action)就是dispatch(action),只是换了一个名字而已。而且留神最初一层返回值return function(action)的构造,他的参数是action,是不是很像dispatch(action),其实他就是一个新的dispatch(action),这个新的dispatch(action)会调用原始的dispatch,并且在调用的前后加上本人的逻辑。所以到这里一个中间件的构造也分明了:

  1. 一个中间件接管store作为参数,会返回一个函数
  2. 返回的这个函数接管老的dispatch函数作为参数,会返回一个新的函数
  3. 返回的新函数就是新的dispatch函数,这个函数外面能够拿到里面两层传进来的store和老dispatch函数

所以说白了,中间件就是增强dispatch的性能,用新的dispatch替换老的dispatch,这不就是个装璜者模式吗?其实后面enhancer也是一个装璜者模式,传入一个createStore,在createStore执行前后加上些代码,最初又返回一个增强版的createStore

遵循这个思路,咱们的applyMiddleware就能够写进去了:

// 间接把后面的构造拿过去function applyMiddleware(middleware) {  function enhancer(createStore) {    function newCreateStore(reducer) {      const store = createStore(reducer);      // 将middleware拿过去执行下,传入store      // 失去第一层函数      const func = middleware(store);      // 解构出原始的dispatch      const { dispatch } = store;      // 将原始的dispatch函数传给func执行      // 失去增强版的dispatch      const newDispatch = func(dispatch);      // 返回的时候用增强版的newDispatch替换原始的dispatch      return {...store, dispatch: newDispatch}    }    return newCreateStore;  }  return enhancer;}

照例用咱们本人的applyMiddleware替换老的,跑起来是一样的成果,阐明咱们写的没问题,哈哈~

反对多个middleware

咱们的applyMiddleware还差一个性能,就是反对多个middleware,比方像这样:

applyMiddleware(  rafScheduler,  timeoutScheduler,  thunk,  vanillaPromise,  readyStatePromise,  logger,  crashReporter)

其实要反对这个也简略,咱们返回的newDispatch外面顺次的将传入的middleware拿进去执行就行,多个函数的串行执行能够应用辅助函数compose,这个函数定义如下。只是须要留神的是咱们这里的compose不能把办法拿来执行就完了,应该返回一个包裹了所有办法的办法。

function compose(...func){  return funcs.reduce((a, b) => (...args) => a(b(...args)));}

这个compose可能比拟让人困惑,我这里还是解说下,比方咱们有三个函数,这三个函数都是咱们后面接管dispatch返回新dispatch的办法:

const fun1 = dispatch => newDispatch1;const fun2 = dispatch => newDispatch2;const fun3 = dispatch => newDispatch3;

当咱们应用了compose(fun1, fun2, fun3)后执行程序是什么样的呢?

// 第一次其实执行的是(func1, func2) => (...args) => func1(fun2(...args))// 这次执行完的返回值是上面这个,用个变量存起来吧const temp = (...args) => func1(fun2(...args))// 咱们下次再循环的时候其实执行的是(temp, func3) => (...args) => temp(func3(...args));// 这个返回值是上面这个,也就是最终的返回值,其实就是从func3开始从右往左执行完了所有函数// 后面的返回值会作为前面参数(...args) => temp(func3(...args));// 再看看下面这个办法,如果把dispatch作为参数传进去会是什么成果(dispatch) => temp(func3(dispatch));// 而后func3(dispatch)返回的是newDispatch3,这个又传给了temp(newDispatch3),也就是上面这个会执行(newDispatch3) => func1(fun2(newDispatch3))// 下面这个外面用newDispatch3执行fun2(newDispatch3)会失去newDispatch2// 而后func1(newDispatch2)会失去newDispatch1// 留神这时候的newDispatch1其实曾经蕴含了newDispatch3和newDispatch2的逻辑了,将它拿进去执行这三个办法就都执行了

所以咱们反对多个middleware的代码就是这样:

// 参数反对多个中间件function applyMiddleware(...middlewares) {  function enhancer(createStore) {    function newCreateStore(reducer) {      const store = createStore(reducer);      // 多个middleware,先解构出dispatch => newDispatch的构造      const chain = middlewares.map(middleware => middleware(store));      const { dispatch } = store;      // 用compose失去一个组合了所有newDispatch的函数      const newDispatchGen = compose(...chain);      // 执行这个函数失去newDispatch      const newDispatch = newDispatchGen(dispatch);      return {...store, dispatch: newDispatch}    }    return newCreateStore;  }  return enhancer;}

最初咱们再加一个logger2中间件实现成果:

function logger2(store) {  return function(next) {    return function(action) {      let result = next(action);      console.log('logger2');      return result    }  }}let store = createStore(reducer, applyMiddleware(logger, logger2));

能够看到logger2也曾经打印进去了,功败垂成。

当初咱们也能够晓得他的中间件为什么要包裹几层函数了:

第一层:目标是传入store参数

第二层:第二层的构造是dispatch => newDispatch,多个中间件的这层函数能够compose起来,造成一个大的dispatch => newDispatch

第三层:这层就是最终的返回值了,其实就是newDispatch,是加强过的dispatch,是中间件的真正逻辑所在。

到这里咱们的applyMiddleware就写完了

总结

  1. 单纯的Redux只是一个状态机,store外面存了所有的状态state,要扭转外面的状态state,只能dispatch action
  2. 对于收回来的action须要用reducer来解决,reducer会计算新的state来代替老的state
  3. subscribe办法能够注册回调办法,当dispatch action的时候会执行外面的回调。
  4. Redux其实就是一个公布订阅模式!
  5. Redux还反对enhancerenhancer其实就是一个装璜者模式,传入以后的createStore,返回一个加强的createStore
  6. Redux应用applyMiddleware反对中间件,applyMiddleware的返回值其实就是一个enhancer
  7. Redux的中间件也是一个装璜者模式,传入以后的dispatch,返回一个加强了的dispatch
  8. 单纯的Redux是没有View层的,所以他能够跟各种UI库联合应用,比方react-redux,打算下一篇文章就是手写react-redux