redux源码阅读

Redux 是可预测的状态管理框架,它很好的解决多交互,多数据源的诉求。

三大原则:

单一数据源:
整个应用的state被存储在一颗object tree中,并且这颗 object tree存在于唯一的store里,store就是存储数据的容器,容器里面会维护整个应用的state。store提供四个API:dispatch subscribe getState replaceReducer。 数据源是唯一的,所以获取数据的唯一方式就是store.getState()。
state只读:
根据state只读原则,state的变更要通过store的dispatch(action)方法,action就是变更数据的载体,action = {type: '', payload},type是变更数据的唯一标志, payload是需要变更的数据。
使用纯函数变更state
reducer是纯函数,接受两个参数即 state和action, 根据action的type属性执行对应的代码,更改相应的数据,然后返回新的state。

完整的数据流过程:

view层触发actionCreator, actionCreator通过store.dispatch(action) ,reducer执行对应的代码,返回新的state,更新页面。


image.png

源码结构:

src
├── utils ---------------------------------------- 工具函数
├── applyMiddleware.js --------------------------- 加载 middleware
├── bindActionCreators.js ------------------------ 生成将 action creator 包裹在 dispatch 里的函数
├── combineReducers.js --------------------------- 合并 reducer 函数
├── compose.js ----------------------------------- 组合函数
├── createStore.js ------------------------------- 创建一个 Redux store 来储存应用中所有的 state
├── index.js ------------------------------------- 入口 js

index.js

index.js文件是整个代码的入口,该文件暴露了一些接口供开发者使用: createStore, combineReducers, bindActionCreators, applyMiddleware, compose,接下来逐个分析这些接口。

createStore

createStore是redux最重要的API,它创建一个store,保存应用中的state。
createStore函数接受三个参数:reducer, preloadedState, enhancer,
返回值:dispatch、subscribe、getState、replaceReducer 和 [$$observable],这就是我们开发中主要使用的几个接口。

参数

reducer: 函数,返回下一个状态,接收state和action作为参数
preloadedState: state初始值,可以忽略不传
enhancer:store的增强器,一般是第三方中间件,返回一个增强后的store creator,这个函数通常由applyMiddleware函数来生成。

返回值

dispatch

function dispatch(action) {
    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?'
      )
    }
    // 如果正在 dispatch 则抛出错误
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }
    // 对抛出 error 的兼容,但是无论如何都会继续执行 isDispatching = false 的操作
    try {
      isDispatching = true
      // 使用 currentReducer 来操作传入 当前状态和 action,返回处理后的状态
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

dispatch函数是触发动作的函数,接收action行为作为参数,action必须是一个对象,且该对象必须有type参数,如果action满足条件,则调用currentReducer(其实就是createStore的参数 reducer的引用),currentReducer会根据action来改变对应的值,生成新的store,然后再遍历nextListeners列表,调用每一个监听函数。

getState

// 读取由 store 管理的状态树
function getState() {
  if (isDispatching) {
    throw new Error(
      'You may not call store.getState() while the reducer is executing. ' +
        'The reducer has already received the state as an argument. ' +
        'Pass it down from the top reducer instead of reading it from the store.'
    )
  }

  return currentState
}

这个函数可以获取当前的状态,createStore 中的 currentState 储存当前的状态树,这是一个闭包,这个参数会持久存在,并且所有的操作状态都是改变这个引用,getState 函数返回当前的 currentState。

subscribe

function subscribe(listener) {
  // 判断传入的参数是否为函数
  if (typeof listener !== 'function') {
    throw new Error('Expected the listener to be a function.')
  }

  if (isDispatching) {
    throw new Error(
      'You may not call store.subscribe() while the reducer is executing. ' +
        'If you would like to be notified after the store has been updated, subscribe from a ' +
        'component and invoke store.getState() in the callback to access the latest state. ' +
        'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
    )
  }

  let isSubscribed = true

  ensureCanMutateNextListeners()
  nextListeners.push(listener)

  return function unsubscribe() {
    if (!isSubscribed) {
      return
    }

    if (isDispatching) {
      throw new Error(
        'You may not unsubscribe from a store listener while the reducer is executing. ' +
          'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
      )
    }

    isSubscribed = false

    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
  }
}

除去一些边界条件的判断,subscribe函数最主要的是给store状态添加监听函数,该函数接收一个函数作为参数,会往nextListeners监听列表加入这个函数,然后会返回一个unsubscribe函数,用于解绑,如果解绑,就从nextListeners列表中去掉该函数,一旦调用dispatch改变store,监听函数就会全部执行。

combineReducers

reducer是管理state的一个模块,在应用初始化的时候,它返回initialState,当用户调用action时,它会根据action的type属性,进行相应的更新,reducer是纯函数,不会更改传入的state,会返回新的state。
当所有的reducer逻辑都写在同一个reducer函数里面会非常的庞大,所以我们会将reducer进行适当的拆分,但是最终传入createStore里面的是唯一的reducer函数,所以我们传入createStore前要进行合并,就需要combineReducers方法。

参数
reducers (Object): 一个对象,它的值(value)对应不同的 reducer 函数,这些 reducer 函数后面会被合并成一个。

返回值
(Function): 它是真正 createStore 函数的 reducer,接受一个初始化状态和一个 action 参数;每次调用的时候会去遍历 finalReducer(有效的 reducer 列表),然后调用列表中每个 reducer,最终构造一个与 reducers 对象结构相同的 state 对象。

combineReducers用法

rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer})
// This would produce the following state object
{
  potato: {
    // ... potatoes, and other state managed by the potatoReducer ...
  },
  tomato: {
    // ... tomatoes, and other state managed by the tomatoReducer, maybe some nice sauce? ...
  }
}

源码

export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

   /* if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }*/

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  /*let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }*/

  return function combination(state = {}, action) {
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    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]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

combineReducer函数返回一个combination 函数,该函数接收state和action作为参数,每次调用该函数时:

1、 for (let i = 0; i < finalReducerKeys.length; i++) { ... }:遍历 finalReducer(有效的 reducer 列表);
2、 var previousStateForKey = state[key]:当前遍历项的之前状态,看到这里就应该明白传入的 reducers 组合为什么 key 要和 store 里面的 state 的 key 相对应了;
3、 var nextStateForKey = reducer(previousStateForKey, action):当前遍历项的下一个状态;
4、 nextState[key] = nextStateForKey:将 当前遍历项的下一个状态添加到 nextState;
5、 hasChanged = hasChanged || nextStateForKey !== previousStateForKey:判断状态是否改变;
6、 return hasChanged ? nextState : state:如果没有改变就返回原有状态,如果改变了就返回新生成的状态对象。

applyMiddleware

bindActionCreators

  1. actionCreator创建动作
    在分析之前先明确ActionCreator是什么?ActionCreator是动作创造者或者说是动作工厂,如果我们想根据不同的参数来生成不同值的计数器,例子如下:
  const counterActionCreator = (step) => {
    return {
      type: 'increment',
      step:  step || 1,
    }
  }

2.bindActionCreator
从上述例子出发,如果我们想生成不同的计数器,并分发他们,则需要按照以下写法:

const action1 = counterActionCreator();
dispatch(action1);

const action2 = counterActionCreator(2);
dispatch(action2);

const action3 = counterActionCreator(3);
dispatch(action3);

从上面的写法来看,每次都需要去调用actionCreator,再调用dispatch分发action,会产生很多繁杂重复的代码,所以我们可以采用 bindActionCreators.js 文件里面的bindActionCreator 方法来优化代码,

bindActionCreator 源码:
function bindActionCreator(actionCreator, dispatch) {
  return function() {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

bindActionCreator会返回一个函数,函数中会调用actionCreator生成action,并将action作为dispatch的参数分发掉,所以我们将action的生成动作和调用动作封装到了一个函数里面,我们直接调用bindActionCreator返回的函数就行,不用再每次去调用actionCreator,再调用dispatch分发action,例子如下:

const increment = bindActionCreator(counterActionCreator, dispatch);

increment();
increment(2);
increment(3);
  1. bindActionCreators
    接下来看bindActionCreators函数,其实是对bindActionCreator的增强,去掉一些判断条件,源码部分具体如下:
function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') { // #1
    return bindActionCreator(actionCreators, dispatch) // #2
  }

  ....

  const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') { // #3
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}

当actionCreators是一个函数时,直接调用bindActionCreator将整个过程封装返回即可
当actionCreators是一个有多个函数方法组成的对象时,遍历该对象,如果键对应的值是函数,则调用bindActionCreator将整个过程封装,并将封装结果赋值给该键,最后返回一个对象,对象里面的值都被bindActionCreator封装过。
actionCreators为对象时的例子如下:

const actionCreators = {
  increment: function(step) {
    return {
      type: 'INCREMENT',
      step: step || 1
    }
  },

  decrement: function(step) {
    return {
      type: 'DECREMENT',
      step: - (step || 1)
    }
  }
}

const newActionCreators = bindActionCreators(MyActionCreators, dispatch)

newActionCreators.increment();
newActionCreators.increment(1);

newActionCreators.decrement();
newActionCreators.decrement(1);
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容