redux源码分析

动机

应用模块之间需要访问共享数据,采用redux管理数据状态。所有数据保存在store tree中,用于维护数据状态。
“Redux 是 JavaScript 状态容器,提供可预测化的状态管理。”

目录

  • 三大原则

  • combineReducers

  • createStore

  • store方法

  • 数据流

  • middlewares

  • 实现redo&undo

三大原则

单一数据源

整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。

State 是只读的

惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。通过 store.dispatch() 将 action 传到 store。action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作

let action = {
    type: 'CHANGE_TEXT',
    text:'helloworld'
};
store.dispatch(action);

可以通过Action 创建函数 生成 action 。

function changeText(text) {
  return {
    type: 'CHANGE_TEXT',
    text
  }
}
store.dispatch(changeText('helloworld'));

使用纯函数来执行修改

通过reducer改变state tree,要求reducer是纯函数,即只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。

function todoApp(state = initialState, action) {
  switch (action.type) {
    case 'CHANGE_TEXT':
      return Object.assign({}, state, {
        text: action.text
      })
    default:
      return state
  }
}

注意:
1)不要修改 state。
2)在 default 情况下返回旧的 state。

combineReducers

开发一个函数来做为主 reducer,它调用多个子 reducer 分别处理 state 中的一部分数据,然后再把这些数据合成一个大的单一对象。每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。

reducers.js
import { combineReducers } from 'redux'
const initialUser = {
 name:"chen",
 age:10
};
const initialJob = {
 position:"engineer"
};
const userReducer = (state = initialUser, action) => {
 let payload = action.payload;
 let type = action.type;
 switch (type) {
   case "CHANGE_NAME":
     state = Object.assign({},state,{ name : payload});
     break;
   case "ADD_AGE":
     state = Object.assign({},state,{ age : state.age+1});
     break;
   default:
     break;
 }
 return state;
};
const jobReducer = (state = initialJob, action) => {
 let payload = action.payload;
 let type = action.type;
 switch (type) {
   case "CHANGE_POSITION":
     state = Object.assign({},state,{ position : payload});
     break;
   default:
     break;
 }
 return state;
};

const reducers = combineReducers({
 userReducer,
 jobReducer
})

export default reducers

createStore

通过createStore生成store tree。createStore() 的第二个参数是可选的, 用于设置 state 初始状态

const store = createStore(
  reducers
);

store方法

提供 getState() 方法获取 state;
提供 dispatch(action) 方法更新 state;
通过 subscribe(listener) 注册监听器;
通过 subscribe(listener) 返回的函数注销监听器。

数据流

严格的单向数据流
数据流动方向

clipboard.png

问题:无法进行异步或辅助操作。

middlewares

提供了位于 action 被发起之后,到达 reducer 之前的扩展点。 可以用来进行日志记录、创建崩溃报告、调用异步接口或者路由。

以下是3个不同功能的中间件,通过输出数组的方式将3个中间件输出模块

middleware 机制

redux 提供了 applyMiddleware 这个 api 来加载 middleware

applyMiddleware.js源码
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch
    let chain = []

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
middlewares
const m1 = store => next => action => {
  let startState = store.getState();
  console.log("m1 start");
  console.log(startState.user.age);
  next(action);
  let endState = store.getState();
  console.log("m1 end");
  console.log(endState.user.age);
};
const m2 = store => next => action => {
  let startState = store.getState();
  console.log("m2 start");
  console.log(startState.user.age);
  next(action);
  let endState = store.getState();
  console.log("m2 end");
  console.log(endState.user.age);
};
const m3 = store => next => action => {
  let startState = store.getState();
  console.log("m3 start");
  console.log(startState.user.age);
  next(action);
  let endState = store.getState();
  console.log("m3 end");
  console.log(endState.user.age);
};

const middlewares = [m1, m2, m3];

export default middlewares
index.js
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
import middlewares from './middlewares';

import reducers from './reducers';
import middlewares from './middlewares';

const defaultState = {};
const store = applyMiddleware(...middlewares)(createStore)(reducers, defaultState);
window.store = store;

applyMiddleware 是一个多层柯里化(curry)的函数
通过applyMiddleware(...middlewares)可以将[m1,m2,m3]3个中间件串联起来,下面分析以下具体实现方式。
1)初始化store将dispatch指向store.dispatch

const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch

2)用middlewareAPI封装store方法,middlewareAPI.dispatch方法最终指向store.dispatch。

let chain = [] 
const middlewareAPI = {
   getState: store.getState,
   dispatch: (action) => dispatch(action)
}

clipboard.png

3)通过将middlewareAPI传入每个中间件[m1,m2,m3],返回chain,此时chain=[f1,f2,f3],且由于闭包,f1,f2,f3中的store都指向middlewareAPI ,最终指向store。这样的好处就是每个f都能访问到同一个store。

chain = middlewares.map(middleware => middleware(middlewareAPI))

clipboard.png
4)通过compose函数将[f1,f2,f3] 串联执行,最后dispatch被改写成了如下函数。

dispatch = f1(f2(f3(store.dispatch)));
/*
dispatch = (action)=>{
  console.log("m1 start");
  ((action) =>{
    console.log("m2 start");
    ((action) => {
      console.log("m3 start");
      store.dispatch(action)
      console.log("m3 end");
    })(action);
    console.log("m2 end");
  })(action);
  console.log("m1 end");
}
*/

clipboard.png

可以看到,此时f1’,f2’,f3’ 中的next指向中,只有最后一个f3’是指向store.dispatch,其余next指向前一个f'的输出。f1’,f2’,f3’所有store都指向middlewareAPI,最终getState和dispatch还是指向store。通过这样的方式可以将中间件串联起来。

例子

执行一次dispatch

store.dispatch({type:"ADD_AGE"})

输出结果如下:
clipboard.png

注意

中间件的串联并不是简单依次执行,而是从middlewares数组的右边开始,依次将后一个中间件输出当成的函数(一个接收action的函数)作为前一个的next。

总结

最后,经过采用middleware,我们加入middleware来实现“非纯操作”,如请求异步接口,进队列,出队列,处理数据等。

加入middleware后的数据流

clipboard.png

参考文献

https://zhuanlan.zhihu.com/p/...
https://github.com/reactjs/redux


chenluyan
389 声望22 粉丝