美文网首页
Redux源码

Redux源码

作者: 好奇男孩 | 来源:发表于2021-07-05 21:10 被阅读0次

    在进入正题前,我们首先来看一下在项目中是如何使用 Redux 的,根据使用步骤来讲解源码。

    // 首先把多个 reducer 通过 combineReducers 组合在一起
    const appReducer = combineReducers({
        user: UserReducer,
        goods: GoodsReducer,
        order: OrdersReducer,
        chat: ChatReducer
    });
    // 然后将 appReducer 传入 createStore,并且通过 applyMiddleware 使用了中间件 thunkMiddleware
    // 然后在需要的地方发起 dispatch(action) 引起 state 改变
    export default function configureStore() {
        const store = createStore(
            rootReducer,
            compose(
                applyMiddleware(thunkMiddleware),
                window.devToolsExtension ? window.devToolsExtension() : f => f
            )
        );
        return store;
    }
    
    

    介绍完了使用步骤,接下来进入正题。

    源码解析

    首先让我们来看下 combineReducers 函数

    // 传入一个 object
    export default function combineReducers(reducers) {
     // 获取该 Object 的 key 值
        const reducerKeys = Object.keys(reducers)
        // 过滤后的 reducers
        const finalReducers = {}
        // 获取每一个 key 对应的 value
        // 在开发环境下判断值是否为 undefined
        // 然后将值类型是函数的值放入 finalReducers
        for (let i = 0; i < reducerKeys.length; i++) {
            const 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]
            }
        }
        // 拿到过滤后的 reducers 的 key 值
        const finalReducerKeys = Object.keys(finalReducers)
    
        // 在开发环境下判断,保存不期望 key 的缓存用以下面做警告
        let unexpectedKeyCache
        if (process.env.NODE_ENV !== 'production') {
            unexpectedKeyCache = {}
        }
    
        let shapeAssertionError
        try {
        // 该函数解析在下面
            assertReducerShape(finalReducers)
        } catch (e) {
            shapeAssertionError = e
        }
    // combineReducers 函数返回一个函数,也就是合并后的 reducer 函数
    // 该函数返回总的 state
    // 并且你也可以发现这里使用了闭包,函数里面使用到了外面的一些属性
        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)
                }
            }
            // state 是否改变
            let hasChanged = false
            // 改变后的 state
            const nextState = {}
            for (let i = 0; i < finalReducerKeys.length; i++) {
            // 拿到相应的 key
                const key = finalReducerKeys[i]
                // 获得 key 对应的 reducer 函数
                const reducer = finalReducers[key]
                // state 树下的 key 是与 finalReducers 下的 key 相同的
                // 所以你在 combineReducers 中传入的参数的 key 即代表了 各个 reducer 也代表了各个 state
                const previousStateForKey = state[key]
                // 然后执行 reducer 函数获得该 key 值对应的 state
                const nextStateForKey = reducer(previousStateForKey, action)
                // 判断 state 的值,undefined 的话就报错
                if (typeof nextStateForKey === 'undefined') {
                    const errorMessage = getUndefinedStateErrorMessage(key, action)
                    throw new Error(errorMessage)
                }
                // 然后将 value 塞进去
                nextState[key] = nextStateForKey
                // 如果 state 改变
                hasChanged = hasChanged || nextStateForKey !== previousStateForKey
            }
            // state 只要改变过,就返回新的 state
            return hasChanged ? nextState : state
        }
    }
    
    

    combineReducers 函数总的来说很简单,总结来说就是接收一个对象,将参数过滤后返回一个函数。该函数里有一个过滤参数后的对象 finalReducers,遍历该对象,然后执行对象中的每一个 reducer 函数,最后将新的 state 返回。

    接下来让我们来看看 combinrReducers 中用到的两个函数

    // 这是执行的第一个用于抛错的函数
    function assertReducerShape(reducers) {
    // 将 combineReducers 中的参数遍历
        Object.keys(reducers).forEach(key => {
            const reducer = reducers[key]
            // 给他传入一个 action
            const initialState = reducer(undefined, { type: ActionTypes.INIT })
            // 如果得到的 state 为 undefined 就抛错
            if (typeof initialState === 'undefined') {
                throw new Error(
                    `Reducer "${key}" returned undefined during initialization. ` +
                        `If the state passed to the reducer is undefined, you must ` +
                        `explicitly return the initial state. The initial state may ` +
                        `not be undefined. If you don't want to set a value for this reducer, ` +
                        `you can use null instead of undefined.`
                )
            }
            // 再过滤一次,考虑到万一你在 reducer 中给 ActionTypes.INIT 返回了值
            // 传入一个随机的 action 判断值是否为 undefined
            const type =
                '@@redux/PROBE_UNKNOWN_ACTION_' +
                Math.random()
                    .toString(36)
                    .substring(7)
                    .split('')
                    .join('.')
            if (typeof reducer(undefined, { type }) === 'undefined') {
                throw new Error(
                    `Reducer "${key}" returned undefined when probed with a random type. ` +
                        `Don't try to handle ${
                            ActionTypes.INIT
                        } or other actions in "redux/*" ` +
                        `namespace. They are considered private. Instead, you must return the ` +
                        `current state for any unknown actions, unless it is undefined, ` +
                        `in which case you must return the initial state, regardless of the ` +
                        `action type. The initial state may not be undefined, but can be null.`
                )
            }
        })
    }
    
    function getUnexpectedStateShapeWarningMessage(
        inputState,
        reducers,
        action,
        unexpectedKeyCache
    ) {
        // 这里的 reducers 已经是 finalReducers
        const reducerKeys = Object.keys(reducers)
        const argumentName =
            action && action.type === ActionTypes.INIT
                ? 'preloadedState argument passed to createStore'
                : 'previous state received by the reducer'
    
        // 如果 finalReducers 为空
        if (reducerKeys.length === 0) {
            return (
                'Store does not have a valid reducer. Make sure the argument passed ' +
                'to combineReducers is an object whose values are reducers.'
            )
        }
            // 如果你传入的 state 不是对象
        if (!isPlainObject(inputState)) {
            return (
                `The ${argumentName} has unexpected type of "` +
                {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
                `". Expected argument to be an object with the following ` +
                `keys: "${reducerKeys.join('", "')}"`
            )
        }
            // 将参入的 state 于 finalReducers 下的 key 做比较,过滤出多余的 key
        const unexpectedKeys = Object.keys(inputState).filter(
            key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
        )
    
        unexpectedKeys.forEach(key => {
            unexpectedKeyCache[key] = true
        })
    
        if (action && action.type === ActionTypes.REPLACE) return
    
    // 如果 unexpectedKeys 有值的话
        if (unexpectedKeys.length > 0) {
            return (
                `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
                `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
                `Expected to find one of the known reducer keys instead: ` +
                `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
            )
        }
    }
    
    

    接下来让我们先来看看 compose 函数

    // 这个函数设计的很巧妙,通过传入函数引用的方式让我们完成多个函数的嵌套使用,术语叫做高阶函数
    // 通过使用 reduce 函数做到从右至左调用函数
    // 对于上面项目中的例子
    compose(
            applyMiddleware(thunkMiddleware),
            window.devToolsExtension ? window.devToolsExtension() : f => f
    )
    // 经过 compose 函数变成了 applyMiddleware(thunkMiddleware)(window.devToolsExtension()())
    // 所以在找不到 window.devToolsExtension 时你应该返回一个函数
    export default function compose(...funcs) {
        if (funcs.length === 0) {
            return arg => arg
        }
    
        if (funcs.length === 1) {
            return funcs[0]
        }
    
        return funcs.reduce((a, b) => (...args) => a(b(...args)))
    }
    
    

    然后我们来解析 createStore 函数的部分代码

    export default function createStore(reducer, preloadedState, enhancer) {
      // 第4个参数也是函数时,报错,多个增强函数可以合并成一个
      if (
        (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
        (typeof enhancer === 'function' && typeof arguments[3] === 'function')
      ) {
        throw new Error(
          'It looks like you are passing several store enhancers to ' +
            'createStore(). This is not supported. Instead, compose them ' +
            'together to a single function.'
        )
      }
     // 一般 preloadedState 用的少,判断类型,如果第二个参数是函数且没有第三个参数,就调换位置
      if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
        enhancer = preloadedState
        preloadedState = undefined
      }
    
       // 判断 enhancer 是否是函数
      if (typeof enhancer !== 'undefined') {
        if (typeof enhancer !== 'function') {
          throw new Error('Expected the enhancer to be a function.')
        }
         // 类型没错的话,先执行 enhancer,然后再执行 createStore 函数
        return enhancer(createStore)(reducer, preloadedState)
      }
    
      // 判断 reducer 是否是函数
      if (typeof reducer !== 'function') {
        throw new Error('Expected the reducer to be a function.')
      }
      // 当前 reducer
      let currentReducer = reducer
       // 当前状态
      let currentState = preloadedState
      // 当前监听函数数组
      let currentListeners = []
        // 这是一个很重要的设计,为的就是每次在遍历监听器的时候保证 currentListeners 数组不变
        // 可以考虑下只存在 currentListeners 的情况,如果我在某个 subscribe 中再次执行 subscribe
        // 或者 unsubscribe,这样会导致当前的 currentListeners 数组大小发生改变,从而可能导致
        // 索引出错
      let nextListeners = currentListeners
    
        // reducer 是否正在执行
      let isDispatching = false
    
      /**
       * This makes a shallow copy of currentListeners so we can use
       * nextListeners as a temporary list while dispatching.
       *
       * This prevents any bugs around consumers calling
       * subscribe/unsubscribe in the middle of a dispatch.
       */
      // 如果 currentListeners 和 nextListeners 相同,就赋值回去
      function ensureCanMutateNextListeners() {
        if (nextListeners === currentListeners) {
          nextListeners = currentListeners.slice()
        }
      }
      function replaceReducer(nextReducer) {
        if (typeof nextReducer !== 'function') {
          throw new Error('Expected the nextReducer to be a function.')
        }
    
        currentReducer = nextReducer
        dispatch({ type: ActionTypes.REPLACE })
      }
    
    
      function observable() {
        const outerSubscribe = subscribe
        return {
          subscribe(observer) {
            if (typeof observer !== 'object' || observer === null) {
              throw new TypeError('Expected the observer to be an object.')
            }
    
            function observeState() {
              if (observer.next) {
                observer.next(getState())
              }
            }
    
            observeState()
            const unsubscribe = outerSubscribe(observeState)
            return { unsubscribe }
          },
    
          [$$observable]() {
            return this
          }
        }
      }
    
       // 然后在 createStore 末尾会发起一个 action dispatch({ type: ActionTypes.INIT });
     // 用以初始化 state
      dispatch({ type: ActionTypes.INIT })
    
      //dispatch一个用于初始化的action,相当于调用一次reducer
        //然后将reducer中的子reducer的初始值也获取到
        //详见下面reducer的实现。
      return {
        dispatch,
        subscribe,
        getState,
         //下面两个是主要面向库开发者的方法,暂时先忽略
        replaceReducer,
        [$$observable]: observable
      }
    }
    

    可以看出,createStore方法创建了一个store,但是并没有直接将这个store的状态state返回,而是返回了一系列方法,外部可以通过这些方法(getState)获取state,或者间接地(通过调用dispatch)改变state。
    至于state呢,被存在了闭包中。

    我们再来详细的看看每个模块是如何实现的

    getState

     // 获取state
      function getState() {
        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
      }
    

    dispatch

     // 触发了一个action,因此我们调用reducer,得到的新的state,并且执行所有添加到store中的监听函数。
      function dispatch(action) {
         // 原生的 dispatch 会判断 action 是否为对象
        if (!isPlainObject(action)) {
          throw new Error(
            'Actions must be plain objects. ' +
              'Use custom middleware for async actions.'
          )
        }
    
        if (typeof action.type === 'undefined') {
          throw new Error(
            'Actions may not have an undefined "type" property. ' +
              'Have you misspelled a constant?'
          )
        }
        // 注意在 Reducers 中是不能执行 dispatch 函数的
    // 因为你一旦在 reducer 函数中执行 dispatch,会引发死循环
        if (isDispatching) {
          throw new Error('Reducers may not dispatch actions.')
        }
    // 执行 combineReducers 组合后的函数
        try {
          isDispatching = true
           //调用reducer,得到新state
          currentState = currentReducer(currentState, action)
        } finally {
          isDispatching = false
        }
    // 然后遍历 currentListeners,执行数组中保存的函数
        const listeners = (currentListeners = nextListeners)
         //调用监听数组中的所有监听函数
        for (let i = 0; i < listeners.length; i++) {
          const listener = listeners[i]
          listener()
        }
    
        return action
      }
    

    subscribe

    subscribe返回的是一个取消订阅的方法。取消订阅是非常必要的,当添加的监听器没用了之后,应该从store中清理掉。不然每次dispatch都会调用这个没用的监听器。

      // 添加一个监听函数,每当dispatch被调用的时候都会执行这个监听函数
      function subscribe(listener) {
        // 添加到监听函数数组,
        // 注意:我们添加到了下一次dispatch时才会生效的数组
        if (typeof listener !== 'function') {
          throw new Error('Expected the listener to be a function.')
        }
    
        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)
    
        //subscribe返回的是一个取消订阅的方法。
        //取消订阅是非常必要的,当添加的监听器没用了之后,应该从store中清理掉。
        //不然每次dispatch都会调用这个没用的监听器。
    
        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()
          // 从下一轮的监听函数数组(用于下一次dispatch)中删除这个监听器。
          const index = nextListeners.indexOf(listener)
          nextListeners.splice(index, 1)
        }
      }
    

    在这之前我需要先来介绍一下函数柯里化,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

    function add(a,b) { return a + b }
    add(1, 2) => 3
    // 对于以上函数如果使用柯里化可以这样改造
    function add(a) {
            return b => {
                    return a + b
            }
    }
    add(1)(2) => 3
    // 你可以这样理解函数柯里化,通过闭包保存了外部的一个变量,然后返回一个接收参数的函数,在该函数中使用了保存的变量,然后再返回值。
    
    在redux中使用中间件
    

    还记得redux 的createStore()方法的第三个参数enhancer吗?

    function createStore(reducer, preloadedState, enhancer) {
        if(enhancer是有效的){  
            return enhancer(createStore)(reducer, preloadedState)
        } 
        
        //...
    }
    
    

    在这里,我们可以看到,enhancer(可以叫做强化器)是一个函数,这个函数接受一个「普通createStore函数」作为参数,返回一个「加强后的createStore函数」。

    applyMiddleware,顾名思义,「应用中间件」。输入为若干中间件,输出为enhancer。面来看看它的源码:

    // 这个函数应该是整个源码中最难理解的一块了
    // 该函数返回一个柯里化的函数
    // 所以调用这个函数应该这样写 applyMiddleware(...middlewares)(createStore)(...args)
    export default function applyMiddleware(...middlewares) {
      return createStore => (...args) => {
          //用参数传进来的createStore创建一个store
        const store = createStore(...args)
         //注意,我们在这里需要改造的只是store的dispatch方法
        let dispatch = () => {
          //一个临时的dispatch
          //作用是在dispatch改造完成前调用dispatch只会打印错误信息
          throw new Error(
            'Dispatching while constructing your middleware is not allowed. ' +
              'Other middleware would not be applied to this dispatch.'
          )
        }
         // 每个中间件都应该有这两个函数
          //接下来我们准备将每个中间件与我们的state关联起来(通过传入getState方法),得到改造函数。
        const middlewareAPI = {
          getState: store.getState,
          dispatch: (...args) => dispatch(...args)
        }
         // 把 middlewares 中的每个中间件都传入 middlewareAPI
          //middlewares是一个中间件函数数组,中间件函数的返回值是一个改造dispatch的函数
            //调用数组中的每个中间件函数,得到所有的改造函数
        const chain = middlewares.map(middleware => middleware(middlewareAPI))
         // 和之前一样,从右至左调用每个中间件,然后传入 store.dispatch
    
          //将这些改造函数compose(翻译:构成,整理成)成一个函数
            //用compose后的函数去改造store的dispatch
        dispatch = compose(...chain)(store.dispatch)
    
             // compose方法的作用是,例如这样调用:
            // compose(func1,func2,func3)
            // 返回一个函数: (...args) => func1( func2( func3(...args) ) )
            // 即传入的dispatch被func3改造后得到一个新的dispatch,新的dispatch继续被func2改造...
    
    
    
         // 返回store,用改造后的dispatch方法替换store中的dispatch
        return {
          ...store,
          dispatch
        }
      }
    }
                
    
    
    

    总结一下,applyMiddleware的工作方式是:

    调用(若干个)中间件函数,获取(若干个)改造函数
    把所有改造函数compose成一个改造函数
    改造dispatch方法

    中间件的工作方式是:

    中间件是一个函数,不妨叫做中间件函数
    中间件函数的输入是store的getState和dispatch,输出为改造函数(改造dispatch的函数)
    改造函数输入是一个dispatch,输出「改造后的dispatch」

    相关文章

      网友评论

          本文标题:Redux源码

          本文链接:https://www.haomeiwen.com/subject/qygiultx.html