重读redux源码(一)

前言

对于react技术栈的前端同窗来讲,redux应该是相对熟悉的。其代码之精简和设计之巧妙,一直为你们所推崇。此外redux的注释简直完美,阅读起来比较省事。本来也是强行读了通源码,如今也忘得差很少了。由于最近打算对redux进行些操做,因此又开始重读了redux,收益匪浅。前端

关于redux的基本概念,这里就再也不详细描述了。能够参考Redux 中文文档react

阅读源码感觉

有不少大牛已经提供了不少阅读经验。
我的感受一开始就强行读源码是不可取的,就像我当初读的第一遍redux,只能说食之无味,如今全忘了。redux

应该是对其基础用法比较熟练以后,有问题或者有兴趣时再读比较好,结合文档或者实例,完整的流程走一走。数组

此外直接源码仓库clone下来,本地跑一跑,实在看不懂的断点跟进去。app

对于不理解的地方,多是某些方法不太熟悉,这时候多去找找其具体用法和目的框架

实在不明白的能够结合网上已有的源码实例,和别人的思路对比一下,看本身哪里理解有误差。函数

一句话,但愿读过以后对本身有启发,更深刻的理解和学习,而非只是提及来读过而已。学习

redux 提供了以下方法:ui

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
}
复制代码

下面的文章就是按照Redux 中文文档例子的顺序,来分别看下各方法的实现。spa

action和actionCreater

定义和概念

action 本质上是 JavaScript 普通对象 在 Redux 中的 actionCreater就是生成 action 的方法

//addTodo 就是actionCreater
function addTodo(text) {
  //return的对象即为action
  return {
    type: ADD_TODO,
    text
  }
}
复制代码

在 传统的 Flux 实现中,当调用 action 建立函数时
通常会触发一个 dispatch
Redux 中只需把 action 建立函数的结果传给 dispatch() 方法便可发起一次 dispatch 过程。

dispatch(addTodo(text))
//或者 
const boundAddTodo = text => dispatch(addTodo(text))
复制代码

固然实际使用的时候,通常状况下(这里指的是简单的同步actionCreater)咱们不须要每次都手动dispatch,
react-redux 提供的 connect() 会帮咱们来作这个事情。

里面经过bindActionCreators() 能够自动把多个 action 建立函数 绑定到 dispatch() 方法上。

这里先不涉及connect,咱们一块儿看看bindActionCreators如何实现的。

在看以前,咱们能够大胆的猜一下,若是是咱们要提供一个warper,将两个方法绑定在一块儿会怎么作:

function a (){
   /*.....*/  
};
function b(f){
  /*.....*/ 
  return f()
}
复制代码

b里面调用a(先不考虑其余),经过一个c来绑定一下

function c(){
    return ()=> b(a)
}
复制代码

应该就是这么个样子,那么看一下具体实现

bindActionCreators()

先看源码:

// 绑定单个actionCreator
function bindActionCreator(actionCreator, dispatch) {
  //将方法dispatch中,避免了action建立手动调用。
  return (...args) => dispatch(actionCreator(...args))
}
export default function bindActionCreators(actionCreators, dispatch) {
   // function 说明是单个的actionCreator 直接调用bindActionCreator
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }
   // 校验,不然抛错
  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(`错误提示`)
  }
  //获取keys数组,以便遍历
  var keys = Object.keys(actionCreators)
  var boundActionCreators = {}
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i]
    var actionCreator = actionCreators[key]
    //依次进行校验绑定操做
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  //返回
  return boundActionCreators
}
复制代码

该方法分为两部分

首先是 bindActionCreator

对单个ActionCreator方法封装

function bindActionCreator(actionCreator, dispatch) {
  //将方法dispatch中,避免了action建立手动调用。
  return (...args) => dispatch(actionCreator(...args))
  } 
复制代码

bindActionCreators的actionCreators指望是个对象,即actionCreator,
能够想到下面确定是对该对象进行属性遍历,依次调用bindActionCreator

下面bindActionCreators的动做就是处理该对象

  • typeof actionCreators === 'function'
    单独的方法,直接调用 bindActionCreator结束
  • 若是不是对象或者为null,那么抛错
  • 对于对象,根据key进行遍历,获取包装以后的 boundActionCreators 而后返回
// function 说明是单个的actionCreator 直接调用bindActionCreator
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }
   // 校验,不然抛错
  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(`错误提示`)
  }
  //获取keys数组,以便遍历
  var keys = Object.keys(actionCreators)
  var boundActionCreators = {}
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i]
    var actionCreator = actionCreators[key]
    //依次进行校验绑定操做
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }  
复制代码

这样咱们得到了绑定以后的 actionCreators,无需手动调用dispatch(同步的简单状况下)

reducer

action 只是描述了有事发生及提供源数据,具体如何作就须要reducer来处理(详细介绍就略过了)。
在 Redux 应用中,全部的 state 都被保存在一个单一对象中

当reducer处理多个atcion时,显得比较冗长,须要拆分,以下这样:

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    case TOGGLE_TODO:
      return Object.assign({}, state, {
        todos: state.todos.map((todo, index) => {
          if (index === action.index) {
            return Object.assign({}, todo, {
              completed: !todo.completed
            })
          }
          return todo
        })
      })
    default:
      return state
  }
}  
复制代码

须要拆分的时候,每一个reducer只处理相关部分的state相比于所有state应该更好, 例如:

//reducer1 中 
 function reducer1(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return state.reducer1.a 
      //相比较state只是state.reducer1,显然好一点
      return state.a
 }
复制代码

每一个 reducer 只负责管理全局 state 中它负责的一部分。
每一个 reducer 的 state 参数都不一样,分别对应它管理的那部分 state 数据

这样须要在主函数里,分别对子reducer的入参进行管理,能够以下面这样:

function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}
复制代码

固然redux提供了combineReducers()方法

import { combineReducers } from 'redux'

const todoApp = combineReducers({
  visibilityFilter,
  todos
})
复制代码

那么咱们来看下combineReducers是如何来实现的

combineReducers

仍是把完整的代码放上来

export default function combineReducers(reducers) {
  // 获取reducer的key 不做处理的话是子reducer的方法名
  var reducerKeys = Object.keys(reducers)

  var finalReducers = {}
  // 遍历 构造finalReducers即总的reducer
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i]
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  var finalReducerKeys = Object.keys(finalReducers)

  var sanityError
  try {
    // 规范校验
    assertReducerSanity(finalReducers)
  } catch (e) {
    sanityError = e
  }

  return function combination(state = {}, action) {
    if (sanityError) {
      throw sanityError
    }
    // 警报信息 
    if (process.env.NODE_ENV !== 'production') {
      var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action)
      if (warningMessage) {
        warning(warningMessage)
      }
    }
    /** * 当有action改变时, * 遍历finalReducers,执行reducer并赋值给nextState, * 经过对应key的state是否改变决定返回当前或者nextState * */
    // state改变与否的flag 
    var hasChanged = false
    var nextState = {}
    // 依次处理
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      // 获取对应key的state属性
      var previousStateForKey = state[key]
      // 目的之一,只处理对应key数据
      var nextStateForKey = reducer(previousStateForKey, action)
      // 不能返回undefined,不然抛错
      if (typeof nextStateForKey === 'undefined') {
        var errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      // 新状态赋给 nextState对象
      nextState[key] = nextStateForKey
      // 是否改变处理 
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    // 视状况返回state
    return hasChanged ? nextState : state
  }
}
复制代码

入参

首先看一下入参:reducers

  • 即须要合并处理的子reducer对象集合。
  • 能够经过import * as reducers来获取
  • tips:
    reducer应该对default状况也进行处理, 当state是undefined或者未定义的action时,也不能返回undefined。 返回的是一个总reducer,能够调用每一个传入方法,而且分别传入相应的state属性。

遍历reducers

既然是个对象集合,确定要遍历对象,因此前几步就是这么个操做。

// 获取reducer key 目的在于每一个子方法处理对应key的state
  var reducerKeys = Object.keys(reducers)

  var finalReducers = {}
  // 遍历 构造finalReducers即总的reducer
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i]
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
//获取finalReducers 供下面遍历调用 
var finalReducerKeys = Object.keys(finalReducers)
复制代码

而后是规范校验,做为一个框架这是必须的,能够略过

combination

返回一个function

  • 当action被dispatch进来时,该方法主要是分发不一样state到对应reducer处理,并返回最新state

  • 先是标识变量:

// state改变与否的flag 
    var hasChanged = false
    var nextState = {}  
复制代码
  • 进行遍历finalReducers 保存原来的previousStateForKey

  • 而后分发对应属性给相应reducer进行处理获取nextStateForKey

    先对nextStateForKey 作个校验,由于reducer要求作兼容的,因此不容许undefined的出现,出现就抛错。

    正常的话就nextStateForKey把赋给nextState对应的key

  • 先后两个state作个比较看是否相等,相等的话hasChanged置为true 遍历结束以后就得到了一个新的state即nextState

for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      // 获取对应key的state属性
      var previousStateForKey = state[key]
      // 目的之一,只处理对应key数据
      var nextStateForKey = reducer(previousStateForKey, action)
      // 不能返回undefined,不然抛错
      if (typeof nextStateForKey === 'undefined') {
         //.....
      }
      // 新状态赋给 nextState对象
      nextState[key] = nextStateForKey
      // 是否改变处理 
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
复制代码

根据hasChanged来决定返回新旧state。

// 视状况返回state
    return hasChanged ? nextState : state
复制代码

到这里combineReducers就结束了。

结束语

此次先分享一半,仍是有点多的,剩下的下次再记录一下。抛砖引玉,提高本身,共同窗习吧。

参考文章

Redux 中文文档

相关文章
相关标签/搜索