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

2018-04-09  本文已影响363人  fangkyi03

今天来讨论一下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

上一篇下一篇

猜你喜欢

热点阅读