大道至简之redux源码分析

知乎:https://zhuanlan.zhihu.com/p/31025366

1. 背景

对于大型复杂应用程序来说,我们经常需要共享某些组件的状态或者一个组件需要改变另外一个组件的状态。 如果没有一个合适的管理工具管理状态,很快就会变成一团乱。使用 redux 的开发者越来越多,但背后 redux 原理和为什么用 redux 很多人其实并不清楚。如果我们不能完全理解 redux 的思想,可能在实际的应用中,会盲目使用。本文主要结合 redux 源码分析 redux 原理,帮忙大家更好的使用 redux。

2. Redux 核心概念

2.1 Store

Store 在中文里是储备的意思,Store 其实就是一个保存数据的仓库,按照 redux 设计理念,Store 只能有一个。下面是经过简化的 createStore 源码,方便大家理解。

// reducer 是纯函数
export default function createStore(reducer) {
  //当前Reducer
  let currentReducer = reducer
  //当前state
  let currentState

  //获取当前state
  function getState() {
    return currentState
  }

  /**
   * 用来触发状态改变
   *
   * @param {Object} action 
   *
   * @returns {Object} 原来action
   */
  function dispatch(action) {
    currentState = currentReducer(currentState, action)
    return action
  }

  // 初始化 state tree.
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState
  }
}

复制代码

Store 是一个由 createStore 函数创建生成的对象,包括了 dispatch, subscribe, getState等属性。createStore 第一个参数是纯函数 reducer,经过 dispatch 初始化,调用 reducer 函数生成了新的 State。

2.2 State

Store 包括了所有的数据,如果要得到某个时点的数据,就要对 Store 生成快照。这个时点的数据集合,就叫 State。根据上文的源码,我们知道通过调用 store.getState() 就能获取到当前的 state 对象。

2.3 Action

用户不能修改 view, 不能直接修改 state,于是就有了 action 的概念。action 是一个对象,其中 type 属性是必须的。

 /**
   * 用来触发状态改变
   *
   * @param {Object} action 
   *
   * @returns {Object} 原来action
   */
  function dispatch(action) {
    currentState = currentReducer(currentState, action)
    return action
  }
复制代码

dispatch(action) 后,会调用当前的 reducer 根据 state 和 action ,生成新的 state;

2.4 Action Creator

如果每一个Action都需要手写,会非常麻烦,可以定义一个函数生成 Action,这个函数就叫 Action Creator,下面是用法示例。

const ADD_TODO = '添加 TODO';

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

const action = addTodo('Learn Redux');

复制代码

2.5 bindActionCreator

这个函数作用是简化 store.dispatch 调用, 将一个或一组actionCreator跟 dispatch 绑定

//将dispatch 绑定到 actionCreator
function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

export default function bindActionCreators(actionCreators, dispatch) {
  // 如果actionCreators是一个函数,直接返回 bindActionCreator处理后的函数
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(
      `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
      `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
    )
  }

  const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  // 遍历对象,然后对每个遍历项的 actionCreator 生成函数,将函数按照原来的 key 值放到一个对象中,最后返回这个对象
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    } else {
      warning(`bindActionCreators expected a function actionCreator for key '${key}', instead received type '${typeof actionCreator}'.`)
    }
  }
  return boundActionCreators
}

复制代码

2.6 Store.dispatch

定义了 Action,那么如何执行 Action呢?dispatch 是 Redux 执行 action 唯一方法。下面是 dispatch 函数全部源码,整个逻辑非常简单。使用 reducer 函数处理当前 state,处理完成后,执行订阅的 listeners。

  function dispatch(action) {
    //检查是否是纯粹的object
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }
    //检查action.type是否定义
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      )
    }
    
    //如果正在dispath,抛出错误
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      //调用currentReducer传给curentState
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    //将下一个监听器传给当前监听器,使用for循环依次执行listener
    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

复制代码

2.7 Reducer

Store 收到 Action 之后,需要计算一个新的 Action, 这种计算 State 变化的函数,就叫 Reducer。


const chatReducer = (state = defaultState, action = {}) => {
  const { type, payload } = action;
  switch (type) {
    case ADD_CHAT:
      return Object.assign({}, state, {
        chatLog: state.chatLog.concat(payload)
      });
    case CHANGE_STATUS:
      return Object.assign({}, state, {
        statusMessage: payload
      });
    case CHANGE_USERNAME:
      return Object.assign({}, state, {
        userName: payload
      });
    default: return state;
  }
};

复制代码

2.8 combineReducers

一个大型应用是由很多组件组成的,如果全部写到一个 reducers 方法,不易维护。通过 redux 提供的 combineReducers 方法可以将多个 reducers 合并成一个 reducers。

/**
 * 这个函数可以组合一组 reducers(对象) ,然后返回一个新的 reducer 函数给 createStore 使用。
 *
 * @param {Object} reducers reducers 组成的对象
 *
 * @returns {Function} 返回一个组合后的reducer .
 */
export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]
    //如果reducers[key]是函数,保存到finalReducers
    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循环遍历,根据key取出的reducer和旧的state,然后执行reduder函数获取新的state。如果state有变化,返回新的state
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

复制代码

3. 流程图:

最后打个小广告

欢迎使用作者开发的接口管理平台: http://yapi.qunar.com

参考

  • https://redux.js.org/docs/basics/Store.html
  • http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值