资讯专栏INFORMATION COLUMN

解析 Redux 源码

Batkid / 2817人阅读

摘要:也可以看我的博客解析源码解析源码是状态容器,提供可预测化的状态管理。作为全家桶的一份子,可谓说也是名声响响,在年学习想必没有多少人没听过吧。

也可以看我的博客 - 解析 Redux 源码

解析 Redux 源码

TIP

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

作为 React 全家桶的一份子,Redux 可谓说也是名声响响,在 2016 年学习 JavaScript 想必没有多少人没听过吧。

这里,本文不是来教大家如何使用 Redux 的 API 的,这一类的文章已经很多,对于 Redux 的介绍和学习可以点击下列链接:

官方文档

官方Github

中文文档

redux 大法好 —— 入门实例 TodoList(本人的渣渣文章)

Redux 体小精悍(只有2kB)且没有任何依赖,因此本文想通过阅读 Redux 的源码来学习 Redux 的使用以及思想。

源码结构

Redux 的源码结构很简单,我们可以直接看 src 目录下的代码:

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

这个是整个代码的入口:

import createStore from "./createStore"
import combineReducers from "./combineReducers"
import bindActionCreators from "./bindActionCreators"
import applyMiddleware from "./applyMiddleware"
import compose from "./compose"
import warning from "./utils/warning"

function isCrushed() {}

if (
  process.env.NODE_ENV !== "production" &&
  typeof isCrushed.name === "string" &&
  isCrushed.name !== "isCrushed"
) {
  warning(
    "。。。"
  )
}

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
}

这里的 isCrushed 函数主要是为了验证在非生产环境下 Redux 是否被压缩(因为如果被压缩了那么 (isCrushed.name !== "isCrushed") 就是 true),如果被压缩会给开发者一个 warn 提示)。

然后就是暴露 createStore combineReducers bindActionCreators applyMiddleware compose 这几个接口给开发者使用,我们来逐一解析这几个 API。

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.")
    }

    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 ...

  dispatch({ type: ActionTypes.INIT })

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

首先定义了一个 ActionTypes 对象,它是一个 action,是一个 Redux 的私有 action,不允许外界触发,用来初始化 Store 的状态树和改变 reducers 后初始化 Store 的状态树。

createStore

然后着重来看 createStore 函数:

接受

它可以接受三个参数,reducer、preloadedState、enhancer:

reducer:是一个函数,返回下一个状态,接受两个参数:当前状态 和 触发的 action;

preloadedState:初始状态对象,可以很随意指定,比如服务端渲染的初始状态,但是如果使用 combineReducers 来生成 reducer,那必须保持状态对象的 key 和 combineReducers 中的 key 相对应;

enhancer:store 的增强器函数,可以指定为 第三方的中间件,时间旅行,持久化 等等,但是这个函数只能用 Redux 提供的 applyMiddleware 函数来生成;

根据传入参数的个数和类型,判断 reducer 、 preloadedState 、 enhancer;

返回

调用完函数它返回的接口是 dispatch subscribe getState replaceReducer[$$observable]

这也是我们开发中主要使用的几个接口。

enhancer

如果 enhancer 参数传入并且是个合法的函数,那么就是调用 enhancer 函数(传入 createStore 来给它操作),enhancer 函数返回的也是一个函数,在这里传入 reducerpreloadedState,并且返回函数调用结果,终止当前函数执行;
在 enhancer 函数里面是如何操作使用的可以看 applyMiddleware 部分;

getState
function getState() {
  return currentState
}

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

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

  var isSubscribed = true

  ensureCanMutateNextListeners()
  nextListeners.push(listener)

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

    isSubscribed = false

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

这个函数可以给 store 的状态添加订阅监听函数,一旦调用 dispatch ,所有的监听函数就会执行;
nextListeners 就是储存当前监听函数的列表,调用 subscribe,传入一个函数作为参数,那么就会给 nextListeners 列表 push 这个函数;
同时调用 subscribe 函数会返回一个 unsubscribe 函数,用来解绑当前传入的函数,同时在 subscribe 函数定义了一个 isSubscribed 标志变量来判断当前的订阅是否已经被解绑,解绑的操作就是从 nextListeners 列表中删除当前的监听函数。

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
  }

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

  return action
}

这个函数是用来触发状态改变的,他接受一个 action 对象作为参数,然后 reducer 根据 action 的属性 以及 当前 store 的状态来生成一个新的状态,赋予当前状态,改变 store 的状态;
currentState = currentReducer(currentState, action)
这里的 currentReducer 是一个函数,他接受两个参数:当前状态 和 action,然后返回计算出来的新的状态;
然后遍历 nextListeners 列表,调用每个监听函数;

replaceReducer
function replaceReducer(nextReducer) {
  // 判断参数是否是函数类型
  if (typeof nextReducer !== "function") {
    throw new Error("Expected the nextReducer to be a function.")
  }

  currentReducer = nextReducer
  dispatch({ type: ActionTypes.INIT })
}

这个函数可以替换 store 当前的 reducer 函数,首先直接把 currentReducer = nextReducer,直接替换;
然后 dispatch({ type: ActionTypes.INIT }) ,用来初始化替换后 reducer 生成的初始化状态并且赋予 store 的状态;

observable
function observable() {
  var outerSubscribe = subscribe
  return {
    subscribe(observer) {
      if (typeof observer !== "object") {
        throw new TypeError("Expected the observer to be an object.")
      }

      function observeState() {
        if (observer.next) {
          observer.next(getState())
        }
      }

      observeState()
      var unsubscribe = outerSubscribe(observeState)
      return { unsubscribe }
    },

    [$$observable]() {
      return this
    }
  }
}

对于这个函数,是不直接暴露给开发者的,它提供了给其他观察者模式/响应式库的交互操作,具体可看 https://github.com/zenparsing/es-observable

last

最后执行 dispatch({ type: ActionTypes.INIT }),用来根据 reducer 初始化 store 的状态。

compose.js

compose 可以接受一组函数参数,从右到左来组合多个函数,然后返回一个组合函数;

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0, -1)
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}
applyMiddleware.js
export default function applyMiddleware(...middlewares) {
  // 这个返回的函数就是 enhancer,接受 createStore 函数,再返回一个函数,接受的其实只有 reducer 和 preloadedState;
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch
    var chain = []

    // 暴漏 getState 和 dispatch 给 第三方中间价使用
    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    // 创造第三方中间件使用 middlewareAPI 后返回的函数组成的数组
    chain = middlewares.map(middleware => middleware(middlewareAPI))

    // 结合这一组函数 和 dispatch 组成的新的 dispatch,然后这个暴漏给用户使用,而原有的 store.dispatch 是不变的,但是不暴漏
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

applyMiddleware 函数的作用是组合 多个 中间件等等,然后返回一个函数(enhancer

还记得在 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 === applyMiddleware(...)

然后再执行 enhancer(createStore) 继续之后的操作;

这里 enhancer(createStore) 等同于 (reducer, preloadedState, enhancer) => { ... }

然后再执行 enhancer(createStore)(reducer, preloadedState)

再回到 applyMiddleware ,这里调用了 var store = createStore(reducer, preloadedState, enhancer)
如上所见,这里执行的时候已经没有 enhancer 参数了,因此会再次执行 createStore 函数的全部部分,然后得到一个返回的实例 store

之后会生成一个新的 dispatch ,先保存下来原生的 dispatchvar dispatch = store.dispatch

var middlewareAPI = {
  getState: store.getState,
  dispatch: (action) => dispatch(action)
}

这一步是把 store 的 getStatedispatch 接口暴露给中间件来操作: chain = middlewares.map(middleware => middleware(middlewareAPI))

最后组合 全部中间件的返回值(函数)chainstore.dispatch,然后返回新的 dispatchdispatch = compose(...chain)(store.dispatch)

这里的 dispatch 并不是原有的 store 的,而是经过组合中间件之后新的 dispatch

最后返回暴露给用户的接口:

return {
  ...store,
  dispatch
}

主要还是 store 原有的接口,但是用新的 dispatch 替换了原有的;这个函数其实就是根据中间件和store的接口生成新的 dispatch 然后暴露给用户。

combineReducers.js

这个函数可以组合一组 reducers(对象) ,然后返回一个新的 reducer 函数给 createStore 使用。

它接受一组 reducers 组成的对象,对象的 key 是对应 value(reducer函数)的状态名称;

比如: { userInfo: getUserInfo, list: getList }

userInfo 是根据 getUserInfo 函数计算出来的;

那么 store 里面的 state 结构就会是: { userInfo: ..., list: ... }

export default function combineReducers(reducers) {

  // 根据 reducers 生成最终合法的 finalReducers:value 为 函数
  var reducerKeys = Object.keys(reducers)
  var finalReducers = {}
  for (var i = 0; i < reducerKeys.length; i++) {
    var 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]
    }
  }

  var finalReducerKeys = Object.keys(finalReducers)

  if (process.env.NODE_ENV !== "production") {
    var unexpectedKeyCache = {}
  }

  // 验证 reducer 是否合法
  var sanityError
  try {
    assertReducerSanity(finalReducers)
  } catch (e) {
    sanityError = e
  }

  // 返回最终生成的 reducer
  return function combination(state = {}, action) {
    if (sanityError) {
      throw sanityError
    }

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

    var hasChanged = false
    var nextState = {}
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      var previousStateForKey = state[key]
      var nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === "undefined") {
        var errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    // 遍历一遍看是否改变,然后返回原有状态值或者新的状态值
    return hasChanged ? nextState : state
  }
}

最终返回一个 combination 也就是真正传入 createStore 的 reducer 函数;

这是一个标准的 reducer 函数,接受一个初始化状态 和 一个 action 参数;

每次调用的时候会去遍历 finalReducer (有效的 reducer 列表),获取列表中每个 reducer 对应的先前状态: var previousStateForKey = state[key]
看到这里就应该明白传入的 reducers 组合为什么 key 要和 store 里面的 state 的 key 相对应;

然后得到当前遍历项的下一个状态: var nextStateForKey = reducer(previousStateForKey, action)
然后把它添加到整体的下一个状态: nextState[key] = nextStateForKey

每次遍历会判断整体状态是否改变: hasChanged = hasChanged || nextStateForKey !== previousStateForKey

在最后,如果没有改变就返回原有状态,如果改变了就返回新生成的状态对象: return hasChanged ? nextState : state

bindActionCreators.js

bindActionCreators 函数可以生成直接触发 action 的函数;

实质上它只说做了这么一个操作 bindActionFoo = (...args) => dispatch(actionCreator(...args))

因此我们直接调用 bindActionFoo 函数就可以改变状态了;

接受两个参数,一个是 actionCreators( actionCreator 组成的对象,key 对于生成的函数名/或者是一个 actionCreator ),一个是 dispatch, store 实例中获取;

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

export default function bindActionCreators(actionCreators, dispatch) {
  // 是一个函数,直接返回一个 bindActionCreator 函数,这个函数调用 dispatch 触发 action
  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"?`
    )
  }

  // 遍历对象,然后对每个遍历项的 actionCreator 生成函数,将函数按照原来的 key 值放到一个对象中,最后返回这个对象
  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
}
总结

阅读了一遍 Redux 的源码,实在是太精妙了,少依赖,少耦合,纯函数式。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92349.html

相关文章

  • redux源码解读--applyMiddleware源码解析

    摘要:的中间件主要是通过模块实现的。返回的也是一个对象这个其实就是,各个中间件的最底层第三层的哪个函数组成的圆环函数构成的这就是对源码的一个整体解读,水平有限,欢迎拍砖。后续的源码解读和测试例子可以关注源码解读仓库 applyMiddleware源码解析 中间件机制在redux中是强大且便捷的,利用redux的中间件我们能够实现日志记录,异步调用等多种十分实用的功能。redux的中间件主要是...

    Atom 评论0 收藏0
  • 源码解析redux-thunk

    摘要:的返回值是函数,这个函数经调用,传入参数,之后会在中间件链上进行传递,只要保证每个中间件的参数是并且将传递给下一个中间件。 了解了Redux原理之后,我很好奇Redux中间件是怎么运作的,于是选了最常用的redux-thunk进行源码分析。 此次分析用的redux-thunk源码版本是2.2.0,redux源码版本是3.7.2。并且需要了解Redux原理 redux中间件都是由redu...

    simpleapples 评论0 收藏0
  • redux源码解读--compose源码解析

    摘要:源码解析模块的代码十分简练,但是实现的作用却是十分强大。只传递一个参数的时候,就直接把这个函数返回返回组合函数这就是对源码的一个整体解读,水平有限,欢迎拍砖。后续的源码解读和测试例子可以关注源码解读仓库 compose源码解析 compose模块的代码十分简练,但是实现的作用却是十分强大。redux为何称为redux?有人说就是reduce和flux的结合体,而reduce正是comp...

    lk20150415 评论0 收藏0
  • redux源码解读--createStore源码解析

    摘要:源码解析是最核心的模块。比如,当我们需要使用中间件的时候,就会像第三个参数传递一个返回值是一个。后续的源码解读和测试例子可以关注源码解读仓库 createStore源码解析 createStore是redux最核心的模块。这个模块就是用于创建一个store对象,同时,对外暴露出dispatch,getState,subscribe和replaceReducer方法。(源码中关于obse...

    tianren124 评论0 收藏0
  • Redux 源码解析 - Redux 的架构

    摘要:要应用于生成环境必须要用或者,是的进化产物,优于。我们来看一下他的源码,从而学一些东西。里面都是一个一个的模块,一共个模块,都导出了一些的方法,比如这个号函数,一个匿名函数,然后导出他写的方法。整体架构就是这样的。主要用于压缩的时候。 redux很小的一个框架,是从flux演变过来的,尽管只有775行,但是它的功能很重要。react要应用于生成环境必须要用flux或者redux,red...

    lylwyy2016 评论0 收藏0
  • redux源码解析

    摘要:前言的源码是我阅读过的一些库的源码中,相对简单的。在更新完成后,同时会更新,并依次执行监听者列表。使用新的替换现有的,同时执行是随机的字符串。会为注册监听器,监听器存储在数组中,返回的函数则会注销监听器。使用管道,将逐层的进行包装 showImg(https://segmentfault.com/img/remote/1460000019425043?w=1380&h=810); sh...

    chanjarster 评论0 收藏0

发表评论

0条评论

Batkid

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<