美文网首页
一起学react(3) redux源码分析

一起学react(3) redux源码分析

作者: fangkyi03 | 来源:发表于2018-04-09 15:33 被阅读363次

    今天来讨论一下redux源码部分
    先附上demo 如果大家有问题 可以直接加我QQ:469373256
    demo地址:https://github.com/fangkyi03/redux-demo.git

    • demo
    function funtest(state,action){
        console.log('state',state,action)
        switch (action.type) {
            case 'test':
                return Object.assign(state,{test:action.payload})
            default:
                return state || {}  
        }
    }
    
    const fun1 = (store) => {
        console.log('store',store);
        return (next) =>{
            console.log('next',next)
            return (action) =>{
                next(action)
                console.log('action',action);
            }
        }
    }
    
    const fun2 = (store) => {
        console.log('store',store);
        return (next) =>{
            console.log('next',next)
            return (action) =>{
                next(action)
                console.log('action',action);
            }
        }
    }
    
    const store = createStore(combineReducers({funtest}),{a:1},applyMiddleware(fun1,fun2,fun1))
    console.log('输出state',store.getState());
    console.log('输出action',store.dispatch({type:'test',payload:'asdasd'}));
    console.log('输出state',store.getState())
    
    store.subscribe(()=>{
        console.log('输出store最新变化',store.getState());
    })
    
    • 暴露的接口部分
    export {
      createStore,
      combineReducers,
      bindActionCreators,
      applyMiddleware,
      compose
    }
    

    redux核心暴露出来的接口只有这么几个 接下来将会按照这个demo的执行顺序一一的给大家进行一下解释 先来看一下combineReducers这个函数的使用方法

    • combineReducers
      先附上完整代码 然后在一一讲解
    export default function combineReducers(reducers) {
      const reducerKeys = Object.keys(reducers)
      const 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]
        }
      }
      const finalReducerKeys = Object.keys(finalReducers)
    
      let unexpectedKeyCache
      if (process.env.NODE_ENV !== 'production') {
        unexpectedKeyCache = {}
      }
    
      let shapeAssertionError
      try {
        assertReducerShape(finalReducers)
      } catch (e) {
        shapeAssertionError = e
      }
    
      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)
          }
        }
    
        let hasChanged = false
        const nextState = {}
        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)
          if (typeof nextStateForKey === 'undefined') {
            const errorMessage = getUndefinedStateErrorMessage(key, action)
            throw new Error(errorMessage)
          }
          nextState[key] = nextStateForKey
          hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }
        return hasChanged ? nextState : state
      }
    }
    

    combineReducers主要可以分为两个部分
    1.效验部分 效验你输入的参数是否符合要求,如果不符合就会在使用之前直接抛出异常

    1. combination部分 也就是在每次dispatch的时候 都会执行的这个
      在这里会传入最新的state跟action 每次都会重新的去执行一遍 并且返回最新的一个currentState值 这里也是比较诟病的地方 因为如果你的reduce比较多的情况下 你每次dispatch都会去将所有的reduce重新去走一遍 其实是比较耗费性能的
      将效验可以分成如下几种情况

    1.当执行reduce以后 返回的值为undefinde的时候
    在这里会调用两种模式 一种是直接传递type为init的 一种是传递type为随机数的
    其实主要就是确保你在switch的时候 都加了default类型 避免 出现返回undefined的情况

    function assertReducerShape(reducers) {
      Object.keys(reducers).forEach(key => {
        const reducer = reducers[key]
        const initialState = reducer(undefined, { type: ActionTypes.INIT })
    
        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.`
          )
        }
    
        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.`
          )
        }
      })
    }
    

    2.如果发现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('", "')}"`
        )
      }
    

    3.如果在新传入的state与第一次初始化时init返回的节点不一致的时候 会直接进行错误提醒 但是并不会中断程序的运行

      const unexpectedKeys = Object.keys(inputState).filter(key =>
        !reducers.hasOwnProperty(key) &&
        !unexpectedKeyCache[key]
      )
    
      unexpectedKeys.forEach(key => {
        unexpectedKeyCache[key] = true
      })
    
      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.`
        )
      }
    }
    

    4.如果在combination的运算过程中发现返回的state如果是一个undefined的话会直接报错 这个错误会直接中断程序的运行 所以reduce的要求就是 任何情况下 都不能返回一个空数据

        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)
          if (typeof nextStateForKey === 'undefined') {
            const errorMessage = getUndefinedStateErrorMessage(key, action)
            throw new Error(errorMessage)
          }
          nextState[key] = nextStateForKey
          hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }
        return hasChanged ? nextState : state
      }
    

    这里正常应该讲中间件了 但是这边稍微等一下 因为要先看一下createStore中是如何处理的 才能继续讲applyMiddleware

    • createStore部分
    参数定义
    export default function createStore(reducer, preloadedState, enhancer) 
    
    
    如果preloadedState初始化state是一个函数 并且enhancer值为空的情况下 那么就将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.')
        }
    
        return enhancer(createStore)(reducer, preloadedState)
      }
      
    如果reducer不等于函数则直接报错
      if (typeof reducer !== 'function') {
        throw new Error('Expected the reducer to be a function.')
      }
    
    从这里可以看到createStore有两种写法
    createStore(reducer, preloadedState, enhancer)
    createStore(reducer, enhancer)
    两者的区别是一种带了默认state一种不带
    当然一般用了ssr的话 都会采用第一种 因为这样会更加方便初始化state导入
    
    上面我们看到 如果你有传入applyMiddleware中间件的话 默认会走中间件 并且将我们自身给传递过去 那么我们再看一下中间件的写法
    
    • 中间件完整代码
    export default function applyMiddleware(...middlewares) {
      return (createStore) => (reducer, preloadedState, enhancer) => {
        这里传递了原有的createStore过来以后 又使用它去进行了一次初始化
        const store = createStore(reducer, preloadedState, enhancer)
        let dispatch = store.dispatch
        let chain = []
    
        const middlewareAPI = {
          getState: store.getState,
          dispatch: (action) => dispatch(action)
        }
        这里两句话 才是redux最为经典的地方 将这两个地方就得先看一下中间件的结构
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)
    
        return {
          ...store,
          dispatch
        }
      }
    }
    
    • 中间件代码结构
    const fun1 = (store) => {
        console.log('store',store);
        return (next) =>{
            console.log('next',next)
            return (action) =>{
                next(action)
                console.log('action',action);
            }
        }
    }
    
    const fun2 = (store) => {
        console.log('store',store);
        return (next) =>{
            console.log('next',next)
            return (action) =>{
                next(action)
                console.log('action',action);
            }
        }
    }
    

    按照执行顺序来的话 如果你执行了这个以后
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    实际上返回的是中间件的这部分以后的代码 并且会给所有的中间件传递两个函数一个dispatch 一个getState

        return (next) =>{
            console.log('next',next)
            return (action) =>{
                next(action)
                console.log('action',action);
            }
        }
    
    • compose(...chain)(store.dispatch)
      这个部分其实分为几种类型 1.没有数据,2.单条数据 3.多条数据
      来看一下compose的源码
    export default function compose(...funcs) {
      if (funcs.length === 0) {
        return arg => arg
      }
    
      if (funcs.length === 1) {
        return funcs[0]
      }
    
      return funcs.reduce((a, b) => (...reset) => a(b(...reset)))
    }
    

    1.如果你中间件里面没有数据的话 最走这里返回的arg其实就是你传进去的dispatch

      if (funcs.length === 0) {
        return arg => arg
      }
    
    dispatch = compose(...chain)(store.dispatch)
    

    2.如果你传进去的长度为1的话 说明只有一条数据 那么这里就直接返回对应的那个数据

      if (funcs.length === 1) {
        return funcs[0]
      }
    刚才我们上面已经提到了 我们在
    middlewares.map(middleware => middleware(middlewareAPI))
    执行完以后 我们所有返回的值 其实都已经在next子函数下面了
    所以当我们在这里直接return funcs[0]的时候 实际上就是返回我们的next
    dispatch = compose(...chain)(store.dispatch)
    因为这句代码的关系 我们返回的next被执行了 并且传入了store.dispatch这个参数 然后返回了next里面包裹的action函数 作为新的dispatch函数
    所以从这里可以看到 单条数据的是next等于传入的store.dispatch
    所以你可以在接收到action以后 直接next(action)这种方式 直接让他走对应的reduce
    

    3.多条数据跟单条的有一点差别 单条数据的时候 next 永远是dispatch 但是如果多条的时候 next就成为了 控制你下一个中间件是否执行的关键 因为next在多条里面 传入的不单单是dispatch了 还有可能是你上一个中间件的next部分

    return funcs.reduce((a, b) => (...reset) => a(b(...reset)))
    如果你只有两条数据的时候 这个...reset依旧是你传递进去的store.dispatch
    如果你在多余两条数据传递进去以后 这里的...reset在第一次还是store.dispatch
    在第二次就变成了
    return (next) =>{
            console.log('next',next)
            return (action) =>{
                next(action)
                console.log('action',action);
            }
     }
    在这里是不是觉得有点绕 感觉这样的操作 最后是这么返回一个dispatch的呢
    funcs.reduce((a, b) => (...reset) => a(b(...reset)))
    在这个阶段 其实永远不知道传进去的action到底是什么 这里只是生成了一个嵌套的函数链 最后生成的一个函数 会在每次dispatch的时候 都将所有的中间件全部的执行一次 知道最后一层使用了dispatch的时候 才算是结束 了
    
    dispatch((action)=>{
      return a(b(...arg))(action)
    })
    

    ok 现在redux最核心的部分就讲完了 接下来讲一下回调监听的部分

    • subscribe
      这个部分其实比较简单 就是你传入的任何一个listener只要是一个函数就会 被加入到监听的数组里面 然后返回一个卸载的函数 那么这个监听函数又是在哪里被使用的呢 这里就得看一下createStore中关于dispatch的定义了
     function subscribe(listener) {
       if (typeof listener !== 'function') {
         throw new Error('Expected listener to be a function.')
       }
    
       let isSubscribed = true
    
       ensureCanMutateNextListeners()
       nextListeners.push(listener)
    
       return function unsubscribe() {
         if (!isSubscribed) {
           return
         }
    
         isSubscribed = false
    
         ensureCanMutateNextListeners()
         const index = nextListeners.indexOf(listener)
         nextListeners.splice(index, 1)
       }
     }
    
    • dispatch定义
      我们在讲刚才的compose的时候 有说过 你可以在中间件里面直接用dispatch 也可以直接使用传进去的那个store.dispatch 甚至 你也可以不执行next来阻止接下来的中间件执行 不管如何情况 如果在中间件所有都符合的情况下 这个dispatch肯定会被执行 如果你当前中间件在运行过程中 在currentReducer还没执行完毕的情况下 你又去发起了一条dispatch的话 是会导致奔溃报错的
        if (isDispatching) {
          throw new Error('Reducers may not dispatch actions.')
        }
    
      function 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?'
          )
        }
    
        if (isDispatching) {
          throw new Error('Reducers may not dispatch actions.')
        }
    
        try {
          isDispatching = true
          currentState = currentReducer(currentState, action)
        } finally {
          isDispatching = false
        }
    
        const listeners = currentListeners = nextListeners
        for (let i = 0; i < listeners.length; i++) {
          const listener = listeners[i]
          listener()
        }
        return action
      }
    
    • dispatch 主要看两个部分 currentReducer与listeners
      在这里要说两点 这里其实也是redux性能最大的一个问题所在
      当你每次执行dispatch的时候 都会去生成一个最新的reducer并且还会将所有的监听都去执行一遍 其实这里耗费的性能是很大的 这里的currentReducer就是combination
        try {
          isDispatching = true
          currentState = currentReducer(currentState, action)
        } finally {
          isDispatching = false
        }
    
        const listeners = currentListeners = nextListeners
        for (let i = 0; i < listeners.length; i++) {
          const listener = listeners[i]
          listener()
        }
    
    • 参数定义部分
      let currentReducer = reducer
      let currentState = preloadedState
      let currentListeners = []
      let nextListeners = currentListeners
      let isDispatching = false
    

    在这里 redux源码 基本分析完毕 如果大家有问题 可以直接加我QQ:469373256

    相关文章

      网友评论

          本文标题:一起学react(3) redux源码分析

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