Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

redux 源码分析 #23

Open
Godiswill opened this issue Apr 6, 2020 · 0 comments
Open

redux 源码分析 #23

Godiswill opened this issue Apr 6, 2020 · 0 comments

Comments

@Godiswill
Copy link
Owner

redux 源码分析

原文链接

简介

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。

三大原则

  1. 单一数据源,创建一个 Redux store 来以存放应用中所有的 state。应用中应有且仅有一个 store。
  2. State 是只读的,唯一改变 state 的方法就是触发 action,action.type 作为逻辑说明。
  3. 使用纯函数来执行修改,reducers 来描述 action 如何改变 state tree,reducers 应该是一个纯函数。

对比 mobx

  • 第一条,一般 redux 以 reducer 为拆分维度,然后 combineReducers 合成一个总 reducer 来创建 store,然后传给 React context,全局访问。
    而 mobx 一般对于一个页面或页面级别的组件创建 store,然后在一个总文件中导入,作为一个大对象导出给 context。
  • 第二条,redux 把 reducer 生产的 state 闭包在内部,无法直接访问,只能 dispatch action 驱动 reducer 函数来改变 state,逻辑集中在 reducer 内,可预测维护。
    而 mobx @observable state 可以随意改变,甚至逻辑分散在视图 view 层,例如 onClick={() => (state.status = 1)}。虽然官方建议把逻辑封装
    在 action 中,例如 @action updata() {},但不是一种强约束。
  • 第三条,这里是唯一一个 redux 难以强约束用户的地方,例如 reducer 中 state.xx = 1; return state,应该是 return { ...state, xx: 1}
    而鉴于 mobx 的机制, mobx 更别说了。

所以常说,mobx 常用中小型应用,redux 更适合大型应用。

概览

./src
├── applyMiddleware.ts
├── bindActionCreators.ts
├── combineReducers.ts
├── compose.ts
├── createStore.ts
└── index.ts
  • index.ts,核心的 5 个文件名对应 redux 暴露的关键的 5 个函数。
export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
}
  • createStore,主函数用来创建 redux 实例,利用闭包隐藏数据,暴露接口操作内部数据。使用订阅发布模式,当数据改变时发布通知。
  • combineReducers,组合多个 reducer 函数,reducer 就是一个创建、修改重新创建数据的工厂。数据可以交给多个 reducer 小车间创建。
  • bindActionCreators,当需要隐藏 dispatch 或 store 时用到,往往是传给下一个组件时,让组件无感知 redux 的存在。
  • compose,工具函数
  • applyMiddleware,利用 compose 来实现,store.dispatch 传给最后一个中间件函数,前一个中间件函数能调用后一个中间件函数。

使用

例子都是摘录来的,不想看的可以跳过。

  • createStore
import { createStore } from 'redux';

function reducer(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return state.concat([action.text]);
    default:
      return state
  }
}
// createStore 至少接收一个参数、最多三个。第二个是初始化 state 对象,必须是一个纯对象
let store = createStore(reducer, ['Use Redux']);

store.dispatch({
  type: 'ADD_TODO',
  text: 'Read the docs',
});

console.log(store.getState());
// [ 'Use Redux', 'Read the docs' ]
  • combineReducers
// reducers/todos.js
export default function todos(state = [], action) {
  switch (action.type) {
  case 'ADD_TODO':
    return state.concat([action.text]);
  default:
    return state
  }
}
// reducers/counter.js
export default function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1;
  case 'DECREMENT':
    return state - 1;
  default:
    return state
  }
}
// reducers/index.js
import { combineReducers } from 'redux';
import todos from './todos';
import counter from './counter';

// 这里 combineReducers 会根据传入的 reducer 名称设置为 state 的属性名
// 可以设置别名 combineReducers({ cnt: counter }); 最终 state = { cnt: 0 }
export default combineReducers({
  todos,
  counter
})
// App.js
import { createStore } from 'redux';
import reducer from './reducers/index';

let store = createStore(reducer);
console.log(store.getState());
// {
//   counter: 0,
//   todos: []
// }

store.dispatch({
  type: 'ADD_TODO',
  text: 'Use Redux'
});
console.log(store.getState());
// {
//   counter: 0,
//   todos: [ 'Use Redux' ]
// }
  • bindActionCreators
// actionCreators.js
// action 函数
export function addTodo(text) {
  return {
    type: 'ADD_TODO',
    text
  };
}

export function removeTodo(id) {
  return {
    type: 'REMOVE_TODO',
    id
  };
}
// App.js
const bindAction = bindActionCreators({ addTodo, removeTodo }, dispatch);
<Child {...bindAction} />

// Child.js
const {addTodo, removeTodo} = props.bindAction;
addTodo('text'); 
// 相当于 dispatch( addTodo('text') ); 子组件不知道 redux 的存在,却驱动了数据的更新。
  • applyMiddleware,当有多个参数的时,底层用 compose 来组合。
// dispatch 只接收一个纯对象 
function actionCreator( name ) { return { type: 'ActionName', name } }
store.dispatch(
  actionCreator( '张三' )
);

// 但要根据用户id 异步获取姓名怎么办?

// 使用 redux-thunk 支持 dispatch 接收一个函数,
// 把 store 上 dispatch, getState 的控制权传给该函数
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import * as reducers from './reducers';

const reducer = combineReducers(reducers);
// applyMiddleware 为 createStore 注入了 middleware:
const store = createStore(reducer, applyMiddleware(thunk));

function asyncAction(id) {
  return (dispatch, getState) => ajax(id).then(name => dispatch( actionCreator(name) ));
}

// thunk 打破了 dispatch 只能接收纯对象的约定
store.dispatch(
  asyncAction(123)
);

深入 redux

createStore

  • createStore 概览,典型的发布订阅模式
function createStore(reducer, preloadedState, enhancer) {
  // preloadedState、enhancer 不能同为函数,enhancer 为函数时,第四个参数不能为函数
  if (typeof preloadedState === 'function' && typeof enhancer === 'function' || typeof enhancer === 'function' && typeof arguments[3] === 'function') {
    throw new Error('It looks like you are passing several store enhancers to ' + 'createStore(). This is not supported. Instead, compose them ' + 'together to a single function.');
  }
  // preloadedState、enhancer 同为函数时,preloadedState 复制给 enhancer,preloadedState 置为 undefined
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState;
    preloadedState = undefined;
  }
  // 增强函数 enhancer 不为 undefined 时,执行增强函数,这个主要用来实现中间件的连接
  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 currentReducer = reducer; // 存储当前 reducer,也用于 replaceReducer 替换
  let currentState = preloadedState; // 初始化 state
  let currentListeners = []; // 订阅的回调数组
  let nextListeners = currentListeners; // 避免在发布通知调用用户回调函数时抛错影响了数组
  let isDispatching = false; // 防止在 reducer 中执行,getState、dispatch、subscribe、unsubscribe
  
  // currentListeners 数组浅拷贝,防止 dispatch 阶段执行用户回调抛错
  // 导致 dispatch 中断了,却影响了原来的数组结构 
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice();
    }
  }
  
  // 获取 currentState
  function getState() {}
  
  // 订阅消息,即 listener 入 currentListeners 队列
  function subscribe(listener) {}
  
  // 根据 action.type 执行 reducer 函数,触发 state 更新,让后发布通知即遍历 nextListeners 执行
  function dispatch(action) {}
  
  // 动态更换 reducer,然后根据新的 reducer 初始化一次 state
  function replaceReducer(nextReducer) {}
  
  // 为了支持未来 Observable 新特性,还在提案阶段,不讨论
  // function observable() {}
  
  // type 为一个随机字符串不会和 reducer 中的任何 type 相匹配
  // 如果 reducer 没有默认匹配,则初始化的 state 为 preloadedState
  dispatch({ type: ActionTypes.INIT });
  
  const store = {
    getState,
    subscribe,
    dispatch,
    replaceReducer,
  };
  return store;
}
  • 解释下 isDispatching 的作用,例如你在 reducer 中尝试以下操作,都会抛错
function reducer(state = [], action) {
  // reducer 中执行 getState、dispatch、subscribe、unsubscribe
  action.store && action.store.getState();
  action.store && action.store.dispatch({
    type: 'hello',
    text: 'world',
  });
  action.store && action.store.subscribe(() => {});
  switch (action.type) {
    case 'ADD_TODO':
      return state.concat([action.text]);
    default:
      return state
  }
}
  • getState
function getState() {
  if (isDispatching) {
    throw new Error('reducer 函数中不能执行 getState、dispatch、subscribe、unsubscribe');
  }

  return currentState; // 闭包隐藏内部 state,只能通过暴露 api 获取
}
  • subscribe
function subscribe(listener) {
  if (typeof listener !== 'function') {
    throw new Error('Expected the listener to be a function.');
  }

  if (isDispatching) {
    throw new Error('reducer 函数中不能执行 getState、dispatch、subscribe、unsubscribe');
  }

  let isSubscribed = true; // 注册凭证
  ensureCanMutateNextListeners(); // 确保原回调队列在代码抛错时受到干扰
  
  nextListeners.push(listener); // 发布订阅,必不可少的入队列操作
  
  // 返回取消订阅函数
  return function unsubscribe() {
    if (!isSubscribed) { // 防止 unsubscribe 被多次执行
      return;
    }

    if (isDispatching) {
      throw new Error('reducer 函数中不能执行 getState、dispatch、subscribe、unsubscribe');      
    }

    isSubscribed = false; // 一旦执行 unsubscribe 就注销掉注册凭证
    ensureCanMutateNextListeners();
    // 找到队列中的函数,剔除
    const index = nextListeners.indexOf(listener);
    nextListeners.splice(index, 1);
    currentListeners = null; // currentListeners 会在 dispatch 中恢复
  };
}
  • dispatch
function dispatch(action) {
  if (!isPlainObject(action)) { // 什么是纯对象?1. 字面量定义对象:const obj = {}; 2. const obj = new Object();
    throw new Error('action 必须是个纯对象,异步操作需要自定义中间件');
  }

  if (typeof action.type === 'undefined') {
    throw new Error('必不可少的 type ');
  }

  if (isDispatching) {
    throw new Error('reducer 函数中不能执行 getState、dispatch、subscribe、unsubscribe');
  }

  try { // 防止用户 reducer 抛错
    isDispatching = true; // isDispatching 用来控制 reducer 执行阶段
    currentState = currentReducer(currentState, action);
  } finally {
    isDispatching = false;
  }
  // 发布通知
  const listeners = (currentListeners = nextListeners); // 在 unsubscribe 时 null,这里被恢复
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i];
    listener();
  }

  return action;
}
  • replaceReducer
function replaceReducer(nextReducer) {
  if (typeof nextReducer !== 'function') {
    throw new Error('reducer 必须是个函数');
  }
  currentReducer = nextReducer; // 替换闭包中的 reducer

  // type: ActionTypes.REPLACE 是个独一无二的随机字符
  // 所有 reducer 中,最好有个默认 case
  dispatch({ type: ActionTypes.REPLACE });

  return store;
}

到此最重要的 createStore 函数就实现完毕。接下来分析两个辅助函数 bindActionCreators,combineReducers,最后分析实现中间件功能的 compose、applyMiddleware。

bindActionCreators

function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') { // actionCreators 可以是单个函数
    return bindActionCreator(actionCreators, dispatch);
  }

  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error("bindActionCreators 得是个对象或函数");
  }

  let boundActionCreators = {};

  // 也可以是多个函数的导出,例如 export { actionCreators1, actionCreators2, ... }
  for (const key in actionCreators) {
    const actionCreator = actionCreators[key];

    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
    }
  }

  return boundActionCreators;
}

// 实际上就是形成闭包对外隐藏 actionCreator, dispatch。
// 返回的函数接受 actionCreator 的参数,然后 dispatch。
function bindActionCreator(actionCreator, dispatch) {
  return function (...args) {
    return dispatch(actionCreator.apply(this, args));
  };
}

combineReducers

function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers);
  const finalReducers = {};
  // reducers 是一个包含多个 reducer 函数的对象
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i];

    // 这里会做很多判断,为了简便删了
    // 例如你不能在 reducer 函数里使用 redux 内部自定义的 ActionTypes.INIT
    // reducer 函数默认都应该返回一个非 undefined 的值
    // reducer 函数用户明确知道不需要一个默认值,也应该用 null 代替 undefined
    // 等等
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key];
    }
  }
  const finalReducerKeys = Object.keys(finalReducers);

  // 返回的是一个组合后的 reducer 函数
  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];
      
      // 遍历执行所有的 reducer
      const nextStateForKey = reducer(previousStateForKey, action);
      
      if (typeof nextStateForKey === 'undefined') {
        throw new Error('reducer 不应该给个 undefined');
      }
      nextState[key] = nextStateForKey;
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
    }
    // 如果其中有一个 reducer 返回的新的 state 有变更,则最终返回一个新的 state
    hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length;

    return hasChanged ? nextState : state
  }
}

compose

  • 辅助函数,效果:compose(fn1, fn2, fn3, ...) fn1( fn2( fn3(...) ) ); 即 fn3 函数执行的返回值传给 fn2,接着 fn2 的返回值传给 fn1 执行。
function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg;
  }
  
  if (funcs.length === 1) {
    return funcs[0];
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

applyMiddleware

function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, ...args) => {
    const store = createStore(reducer, ...args);
    let dispatch = () => {
      throw new Error('中间件生成函数不能执行 dispatch 操作');
    };

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)
    };

    const chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain)(store.dispatch);

    return {
      ...store,
      dispatch
    };
  }
}

thunk 中间件

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
  • 我们来串联下,中间件是如何进行的
  1. const store = createStore(reducer, preloadedState, enhancer = applyMiddleware(thunk));
  2. 当 enhancer 存在时,执行 createStore 其实是执行 enhancer(createStore)(reducer, preloadedState);
  3. 所以 applyMiddleware 执行返回的是一个接受 createStore 的函数,返回一个新的 createStore 函数,接收 reducer, preloadedState;
  4. 最终返回一个 store 对象,看看中间件是如何接手管辖权的。
    1. 中间件需要是一个接收 store 对象的函数,遍历执行中间件一遍生成 chain 这样每个中间件就能闭包住 store;
    2. 接着在 compose 中执行了一次,最后一个中间件通过 next 参数接收了 store.dispatch,返回了一个接受 action 的函数赋值给 store;
    3. 此时只有一个 thunk 中间件,如果传一个函数的话,控制权就给了用户,通过参数可以拿到 dispatch, getState。如果 action 是对象,此时 next === 内部的 store.dispatch ,就像原来一样。
    4. 如果有多个中间件,那么 applyMiddleware(a, b),a 中的 next 就是 b 中返回的 action => {} 函数,b 中的 next 就是 store.dispatch。这样就串起来了。
  • 所以一般中间件的书写的套路是 store => next => action => {};

总结

完。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant