Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

redux源码解读 #3

Open
Juliiii opened this issue Oct 6, 2018 · 0 comments
Open

redux源码解读 #3

Juliiii opened this issue Oct 6, 2018 · 0 comments

Comments

@Juliiii
Copy link
Owner

Juliiii commented Oct 6, 2018

背景

最近不想写业务代码了,因为就得去实习了。所以打算开始补补坑。比如自己阅读源码的计划。所以今天来聊聊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 ,也欢迎大家和我讨论

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant