博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
redux源码解读
阅读量:6313 次
发布时间:2019-06-22

本文共 9938 字,大约阅读时间需要 33 分钟。

背景

因为就得去实习了。所以打算开始补补坑。比如自己阅读源码的计划。所以今天来聊聊的源码。后续会有和的源码阅读。搞定这些的话,就开始阅读一个node的库的源码了,比如和。

开始

  • 总览, redux的文件结构

文件看起来貌似不少,其实,要理解redux的内部实现,主要就看 createStore.js

,applyMiddleware.js ,combineReducers.js和compose.js。下面从createStore.js开始看。

  • createStore.js

    export default function createStore(reducer, preloadedState, enhancer) {  // 如果第二个参数没有传入初始的state,而是传入了enhancer(为applyMiddleware调用的返回值), 那就将第二个参数,即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.')    }	// 反之, 执行。注意此处。此处意味着,如果createStore的时候传入了enhancer,是会将createStore传入enhancer中,执行enhancer, 而enhancer的返回值也是一个函数。具体的可以等到下面我们讲解applyMiddleware,看完你就知道到底发生了什么。    return enhancer(createStore)(reducer, preloadedState)  }  // 如果没传入enhancer,就继续下面的逻辑    // reducer是要求为一个函数的,如果不是一个函数,报错  if (typeof reducer !== 'function') {    throw new Error('Expected the reducer to be a function.')  }        .....  .....    // 最后createStore就会返回dispatch,subscribe, getState等几个常用的api  return {    dispatch,    subscribe,    getState,    replaceReducer,    [$$observable]: observable  };    }复制代码

    上面的代码给大家展览了下createStore这个函数大概做了什么,其实就是封装了一些api,最后暴露给用户使用。接下来看一下各个api的实现:

    先看一下私有变量的定义

    let currentReducer = reducer // 就是reducer嘛  let currentState = preloadedState // 就是传入的初始state嘛  let currentListeners = [] // 当前的监听器队列  let nextListeners = currentListeners // 未来的监听器队列  let isDispatching = false //  标志是否正在dispatch复制代码

    getState : 用来获取store中的state的。因为redux是不允许用户直接操作state,对于state的获取,是得通过getState的api来获取store内部的state。

    function getState() {    // 如果正在dispatch的话, 说明新的state正在计算中,现在的state是旧的,为了确保用户能获得新的    // state,所以要加一个判断,如果是正在dispatch的话,就报错,反之,返回现在的state    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  }复制代码

    subscribe :redux提供了用户一个监听state变化的api,这个尤为重要,如果没有这个api的暴露,react-redux应该就比较实现了。

    function subscribe(listener) {    // listener是state变化时的回调,必须是个函数    if (typeof listener !== 'function') {      throw new Error('Expected the listener to be a function.')    }	// 如果是正在dispatch中,就报错。因为要确保state变化时,监听器的队列也必须是最新的。所以监听器的注册要在计算新的state之前。    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)    }  }复制代码

    dispatch : 该函数是与getState对应的,getState是读,那dispatch就是写。redux的改动state,只能通过发起一个dispatch,传达一个action给reducer,reducer会根据action和currentState以及自己的内部实现逻辑,来计算出新的state,从而达到写的目的。

    function dispatch(action) {    // action要求是一个简单对象,而一个简单对象就是指通过对象字面量和new Object()创建的对象,如果不是就报错。    if (!isPlainObject(action)) {      throw new Error(        'Actions must be plain objects. ' +          'Use custom middleware for async actions.'      )    }    // reducer内部是根据action的type属性来switch-case,决定用什么逻辑来计算state的,所以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.')    }    // 这里就是计算新的state,并赋值给currentState    try {      isDispatching = true      currentState = currentReducer(currentState, action)    } finally {      isDispatching = false    }    // state更新了后,就如之前我们所说的subscribe,将注册的回调都触发一遍。大家要注意这里,是都触发一遍哦!这个点了解,react-redux的一些原理会比较容易理解。    const listeners = (currentListeners = nextListeners)    for (let i = 0; i < listeners.length; i++) {      const listener = listeners[i]      listener()    }    return action  }复制代码

    以上就是createStore的大致实现。这个函数难度不大,更多的是一个了解redux的入口。咱们从这个入口来一点点挖掘别的代码的实现。下面先从combineReducers开始

  • combineReducers

    • 这个函数是用来整合多个reducers的, 因为createStore只接受一个reducer。
    • 这个函数分为两部分,第一部分是检验用户传入的reducers的准确性。第二部分就是计算state的逻辑。第一部分大家看一看也就了解了为什么,咱们主要看看第二部分
    export default function combineReducers(reducers) {  ........  ........  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)      }    }    // hasChanged来标志是否计算出了新的state    let hasChanged = false    // 这个就是存储新的state的    const nextState = {}    // emmmm, 就是遍历每一个reducer,把action传进去,计算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)      // 如果某个reducer没有返回新的state,就报错      if (typeof nextStateForKey === 'undefined') {        const errorMessage = getUndefinedStateErrorMessage(key, action)        throw new Error(errorMessage)      }      nextState[key] = nextStateForKey      // 此处来判断是否有新的state      hasChanged = hasChanged || nextStateForKey !== previousStateForKey    }    // 根据该标志,决定返回原来的state, 还是新的state    return hasChanged ? nextState : state  }}复制代码

    这个整合的过程就是将所有的reducer存在一个对象里。当dispatch一个action的时候,通过遍历每一个reducer, 来计算出每个reducer的state, 其中用到的优化就是每遍历一个reducer就会判断新旧的state是否发生了变化, 最后决定是返回旧state还是新state。最后得到的state的数据结构类似存reducer的数据结构,就是键为reducer的名字,值为对应reducer的值。这个部分其实也不难。下面继续,我们看看applyMiddleware的实现

  • applyMiddleware

    这个部分就是用来扩展redux的功能的。因为redux的最原始的功能就是操作state,管理state。如果我们需要在这个基础上,根据需求扩展一些功能,就需要通过使用别人编写好的中间件,或者自己编写的中间件来达到需求。比如,发起一个dispatch时,我们为了方便调试,不愿意每次自己手动console.log出这个action,这个时候编写一个logger中间件,就可以自动打印出每次dispatch发起的action,就很容易方便我们测试。又比如,我们要处理异步的action,就可以使用redux-thunk和redux-saga这类中间件。总之,该函数为我们提供了无限的可能。

    我们一点点来看代码:

    export default function applyMiddleware(...middlewares) {  return createStore => (...args) => {    const store = createStore(...args)    ....    ....    ....    return {      ...store,      dispatch    }  }复制代码

    先看个总览的,注意到applyMiddleware接受不定数量的中间件,然后返回一个接受一个creatStore作为参数,返回一个函数的函数。还记得我们在creatStore的时候么?那里有个场景就是

    if (typeof enhancer !== 'undefined') {    if (typeof enhancer !== 'function') {      throw new Error('Expected the enhancer to be a function.')    }    return enhancer(createStore)(reducer, preloadedState)  }复制代码

    当enhancer不为空且为函数时,就执行该函数,并return回去,作为creatStore的返回值。而这个enhancer是什么呢?翻看redux的文档:

    const store = createStore(reducers, applyMiddleware(a, b, c));复制代码

    哦!enhancer就是applyMiddleware的结果,就是一个 creatStore => (...args) = {}的函数。那看下enhancer的代码:

    return createStore => (...args) => {    const store = createStore(...args)    // 1、也许有的同学一开始看到这个会有点蒙蔽, 我当时看到也是觉得奇怪, 这个dispatch的逻辑不对劲    // 而且, 还把这个dispatch作为middleware的参数传了进去,代表在中间件时使用dispatch的逻辑是这个    // 但是看到下面, dispatch = compose(...chain)(store.dispatch)    // 还行, 根据作用域链, 我们可以知道在中间件中调用dispatch的时候, 其实就是调用了这个dispatch, 而不是一开始声明的逻辑    // 而这个dispatch是已经经过compose的包装的了.逻辑到这里的时候就很清楚了    let dispatch = () => {      throw new Error(        `Dispatching while constructing your middleware is not allowed. ` +          `Other middleware would not be applied to this dispatch.`      )    }    const middlewareAPI = {      getState: store.getState,      dispatch: (...args) => dispatch(...args)    }    // 2、compose是如何将中间件串联在一起的?    // 首先一个最简单的中间件的格式: store => next => action => {}    // 这一行代码就是传入了store, 获得了 next => action => {} 的函数    const chain = middlewares.map(middleware => middleware(middlewareAPI))    // 这一行代码其实拆分成两行    // const composeRes = compose(...chain);    // dispatch = composeRes(store.dispatch);    // 第一行是通过compose, 将一个 这样 next => action => {} 的数组组合成 (...args) => f(g(b(...args))) 这么一个函数    // 第二行通过传入store.dispatch, 这个store.dispatch就是最后一个 next => action => {}的next参数    // 传入后 (...args) => f(g(b(...args)) 就会执行, 执行时, store.dispacth作为b的next传入, b函数结果action => {}会作为    // g的next传入, 以此类推. 所以最后dispatch作为有中间件的store的dispatch属性输出, 当用户调用dispatch时, 中间件就会一个一个    // 执行完逻辑后, 将执行权给下一个, 直到原始的store.dispacth, 最后计算出新的state    dispatch = compose(...chain)(store.dispatch)    return {      ...store,      dispatch    }  }复制代码

    跟着上面的注释,大家应该能弄懂enhancer的原理。我这里总结一下,enhancer接收一个creatStore,会在内部创建一个store,然后对该store进行增强,增强的部位在于dispatch。增强的具体方式是通过compose来构造一个dispatch链,链的具体形式就是**[中间件1,中间件2, ......, 中间件N, store.dispatch]** ,然后将增强的dispatch作为store新的dispatch暴露给用户。那用户每次dispatch的时候,就会依次执行每个中间件,执行完当前的,会将执行权交给下一个,直到reducer中,计算出新的state。

结语

网上讲解redux的源码很多,我这篇也是其中一个,主要是我个人学习源码后的,一种记录方式,加深自己印象,也为了之后忘了可以快速重温。redux其实实现上不难,但是思想上真是精髓。程序员的编码能力是一个刚需,但是设计思想是要借他山之玉,来攻石的。站在巨人的肩膀上看远方,希望自己多阅读他人的源码,在能了解原理更好运用的同时,以后自己也能创造出好用的轮子。谢谢大家花时间观看。另外,附源码地址: ,欢迎大家star和fork ,也欢迎大家和我讨论

转载地址:http://mbexa.baihongyu.com/

你可能感兴趣的文章
如何在 Ubuntu Linux 16.04 LTS 中使用多个连接加速 apt-get/apt
查看>>
《OpenACC并行编程实战》—— 导读
查看>>
机器学习:用初等数学解读逻辑回归
查看>>
如何在 Ubuntu 中管理和使用逻辑卷管理 LVM
查看>>
Oracle原厂老兵:从负面案例看Hint的最佳使用方式
查看>>
把自己Github上的代码添加Cocoapods支持
查看>>
C语言OJ项目参考(2493)四则运算
查看>>
零基础入门深度学习(二):神经网络和反向传播算法
查看>>
find和xargs
查看>>
数据结构例程—— 交换排序之快速排序
查看>>
WKWebView代理方法解析
查看>>
IOS定位服务的应用
查看>>
[SMS&WAP]实例讲解制作OTA短信来自动配置手机WAP书签[附源码]
查看>>
IOS中图片(UIImage)拉伸技巧
查看>>
【工具】系统性能查看工具 dstat
查看>>
基于zepto或jquery的手机端弹出框成功,失败,加载特效
查看>>
php引用(&)
查看>>
Delphi 操作Flash D7~XE10都有 导入Activex控件 shockwave
查看>>
oracle 学习笔记之名词解释
查看>>
MySQL Cluster搭建与测试
查看>>