Skip to content

redux源码解读 #3

@Juliiii

Description

@Juliiii

背景

最近不想写业务代码了,因为就得去实习了。所以打算开始补补坑。比如自己阅读源码的计划。所以今天来聊聊redux的源码。后续会有redux-thunkreact-redux的源码阅读。搞定这些的话,就开始阅读一个node的库的源码了,比如eventproxyanywhere

开始

  • 总览, 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其实实现上不难,但是思想上真是精髓。程序员的编码能力是一个刚需,但是设计思想是要借他山之玉,来攻石的。站在巨人的肩膀上看远方,希望自己多阅读他人的源码,在能了解原理更好运用的同时,以后自己也能创造出好用的轮子。谢谢大家花时间观看。另外,附源码地址:https://github.com/Juliiii/source-plan ,欢迎大家star和fork ,也欢迎大家和我讨论

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions