Redux源码分析

Redux是React核心开发人员的开发的一个JavaScript 状态容器,提供可预测化的状态管理。

Redux短小精悍并且没有任何第三方的依赖。

源码接口

.src
├── utils                #工具函数
├── applyMiddleware.js
├── bindActionCreators.js        
├── combineReducers.js     
├── compose.js       
├── createStore.js  
└── index.js             #入口 js

index.js

对外的整个代码的入口,暴露了 createStore, combineReducers, bindActionCreators, applyMiddleware, compose几个接口给开发者。另外也会warn提示 判断redux是否被压缩。

createStore.js

redux中最重要的一个API了,它创建一个Redux store来存放应用所有的state,整个应用中有且只有一个store。

import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'

// 私有 action 仅供自己初始化使用
export var ActionTypes = {
  INIT: '@@redux/INIT'
}

export default function createStore(reducer, preloadedState, enhancer) {
  // 判断接受的参数个数,来指定 reducer 、 preloadedState 和 enhancer
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  // 如果 enhancer 存在并且适合合法的函数,那么调用 enhancer,并且终止当前函数执行
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    // 如果使用了第三方的库:redux-thunk 则会进入这个流程 enhancer只接受 applyMiddleware函数返回
     return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  // 储存当前的 currentReducer
  var currentReducer = reducer
  // 储存当前的状态
  var currentState = preloadedState
  // 储存当前的监听函数列表
  var currentListeners = []
  // 储存下一个监听函数列表
  var nextListeners = currentListeners
  var isDispatching = false

// 这个函数可以根据当前监听函数的列表生成新的下一个监听函数列表引用
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  ... getState ...

  ... subscribe ...

  ... dispatch ...

  ... replaceReducer ...

  ... observable ...
    // 当store创建了,调用dispatch方法 传递type: ActionTypes.INIT
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}

函数对外暴露方法:dispatch, subscribe, getState等等方法。

  • dispatch

    函数参数传递的是action, action为一个普通的plain object对象。

    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
      }
    
      var listeners = currentListeners = nextListeners
      for (var i = 0; i < listeners.length; i++) {
        var listener = listeners[i]
        listener()
         }
    
      return action
    }
    

    传递dispatch(action) currentState = currentReducer(currentState, action) 传入当前的state 和action 并返回一个新的state。

  • subscribe

    可以给store的状态添加订阅监听函数,一旦调用 ' dispatch ',所有的监听函数就会执行,本身存储了一个挡墙监听函数的列表。 调用subcribe函数传入一个函数作为参数,同时会返回一个unsubscribe函数,用来解绑当前传入的监听函数。

  • getState

    返回当前store存储的currentState

compose.js

函数式编程中的概念,接受一组函数参数,从右到左来组合多个函数,然后返回一个组合函数

applyMiddleware.js

函数的作用是组合多个中间件等等,然后返回一个函数也就是所谓的enhancer。

export default function applyMiddleware(...middlewares) {
    // args 也就是所谓的reducer, preloadedState, enhancer 
    // createStore也就是之前的createStore函数 只是加上了它基础上加上了第三方的使用
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }
    let chain = []
    // 暴露getState dispatch给第三方中间件使用
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

这段代码写的很巧妙:在之前的createStore() 中

if (typeof enhancer !== 'undefined') {
  if (typeof enhancer !== 'function') {
    throw new Error('Expected the enhancer to be a function.')
  }

  return enhancer(createStore)(reducer, preloadedState)
}

所以如果有第三方库,enhancer(createStore)(reducer, preloadedState),接着调用applyMiddleware,所以此处相当于调用了 var store = createStore(reducer, preloadedState, enhancer ) ; 接着会重新生成一个新的dispatch, 首先保存之前的store.dispatch。

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

对所有的第三方中间件都暴露上面两个方法, 同时改造dispatch,使用compose来使第三方中间件从右向左依次激活。

CombineReducers

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 (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

 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]
      const nextStateForKey = reducer(previousStateForKey, action)
      
      // ...
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

首先根据key 生成一个新的finalReducers,finalReducerKeys,然后返回一个新的 combination函数(),新的reducer函数 拿到之前的state状态,然后接着调用reducer函数计算出接下来的state。

 // 获取之前的state的状态
 const previousStateForKey = state[key]
 // 计算获取action的接下来的状态
 const nextStateForKey = reducer(previousStateForKey, action)

通过hasChanged变量来判断是否是返回一个新的nextState对象还是之前就的state 判断的标准也就是所谓的 === 三个等号对引用值的判断。

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

推荐阅读更多精彩内容

  • 前言 本文 有配套视频,可以酌情观看。 文中内容因各人理解不同,可能会有所偏差,欢迎朋友们联系我讨论。 文中所有内...
    珍此良辰阅读 11,892评论 23 111
  • 学习必备要点: 首先弄明白,Redux在使用React开发应用时,起到什么作用——状态集中管理 弄清楚Redux是...
    贺贺v5阅读 8,875评论 9 58
  • http://gaearon.github.io/redux/index.html ,文档在 http://rac...
    jacobbubu阅读 79,887评论 35 198
  • 1 redux使用步骤 React仅仅是一个前端View框架库,可以看做是MVC里面的V。没有解决组件间通信,MV...
    Dabao123阅读 415评论 0 2
  • 一、combineReducersredux.combineReducers(reducerMap) 的作用在于合...
    大饼脸me阅读 117评论 0 0