资讯专栏INFORMATION COLUMN

读redux源码总结

worldligang / 446人阅读

摘要:源码个人觉得后的代码就是,后返回的任然是一个函数,可以接受参数,在后面介绍的代码中有所涉及。源码中的获取对象供后面使用,里的对应着对象里的,方法可以获取,也就是对象树的总数据。

redux介绍

redux给我们暴露了这几个方法

{
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
}

我们来依次介绍下

createStore

创建一个store的写法:

let store = createStore(reducer, preloadedState, enhancer);

createStore中的三个参数reducer, preloadedState, enhancer,后面两个是可选参数,
当我们只传两个参数,并且第二个参数是函数时,例如:

let store = createStore(reducer, enhancer);

通过阅读源码这段

if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
    enhancer = preloadedState
    preloadedState = undefined
  }

我们知道,上面的createStore会被改写成这样:

createStore(educer, 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)
  }

会发现原来的调用方式再次发生了改变,变成了以下这种方式

enhancer(createStore)(reducer, undefined)

所以实例化一个createStore方法,以下两种是等价的:

第一种
const store = createStore(reducers, applyMiddleware(routeMiddleware, sagaMiddleware, postRedirectMiddleware, pageSizeMiddleware));


第二种
const store = applyMiddleware(routeMiddleware, sagaMiddleware, postRedirectMiddleware, pageSizeMiddleware)(createStore)(reducers);

最后,返回的store对象提供了以下几个方法给我们使用

dispatch,
subscribe,
getState,
replaceReducer,

getState用来获取currentState,也就是总state值

subscribe注册多个监听函数,这些函数在开发者调用dispatch时会依次执行

dispatch的入参是一个对象action,直接会执行reducer(action)方法,并且批量执行subscribe监听的内容,dispatch执行结束后会返回一个action

replaceReducer替换当前的reducer,这个方法在异步的单应用中可以使用利用起来,例如,我们不想一次性将所有的reducer交给createStore初始化,而是当异步获取某个页面时,再将这个页面的reducer加上之前的旧reducer,通过replaceReducer方法来替换成最新的。这样做的好处是,devtools里的redux工具不会展示那么多的信息,只会展示访问过页面的信息,更有利于我们的开发,当然了由于reducer的减少,store的体积也会变小,页面执行速度更快。

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({
    a:reducer1,
    b:reducer2
})

reducer1是一个函数,而combineReducers返回的任然是一个函数,只不过将每个reducer都遍历了一遍,最后返回的数据结构为

{
    a:state1,
    b:state2
}

而如果我们把a跟b替换成了一个唯一的路径path,这个path跟项目中每个页面的文件夹对应起来,例如:

combineReducers({
    "component/page1/info/":reducer1,
    "component/page2/user/":reducer2
})

而在取state数据的时候这样来取
state["component/page1/info/"]是不是就可以通过文件夹路径实现模块化了,当然了我们离模块化还差一小步,就是不用人工来写路径path,而是交给构建工具(webpack的loader)来遍历完成。

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) => (...args) => a(b(...args)))
}

个人觉得compose(a,b,c)(d)后的代码就是a(b(c(d))),compose后返回的任然是一个函数,可以接受参数,compose在后面介绍的代码中有所涉及。

applyMiddleware

源码

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }
    let chain = []

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

创建一个store的方法:

let store = applyMiddleware(middleware1,middleware2,middleware3)(createStore)(reducers)

中间件middleware1的代码

function middleware1({ getState }) {
  return (next) => (action) => {
    console.log("will dispatch", action)
    let returnValue = next(action)
    console.log("state after dispatch", getState())
    return returnValue
  }
}

applyMiddleware传入的参数为多个中间件,中间件的作用是在执行reducers中的switch之前,先执行中间件中的代码。

对应源码中的参数来讲的话,
实例参数middleware1、middleware2就相当于源码入参...middlewares,
实例参数createStore对应着源码入参createStore,
实例参数reducers对应着源码入参...args。

源码中的

const store = createStore(...args)

获取store对象供后面使用,middlewareAPI里的getState对应着store对象里的getState,getState()方法可以获取currentState,也就是redux对象树的总数据。

chain = middlewares.map(middleware => middleware(middlewareAPI))

middlewareAPI提供了getState这个方法,供中间件获取currenState,这时候chain获取的数组是一个返回值为函数的函数。

下面这行代码比较精彩

dispatch = compose(...chain)(store.dispatch)

首先将最原始的store.dispatch方法作为入参,

dispatch(action)

就相当于
compose(...chain)(store.dispatch)(action),

同时也相当于
middleware1(middleware2(middleware3((store.dispatch))))(action),

当然这里的middleware1是已经只剩下闭包内的两层函数不是原来的三层函数体。

最先执行的是middleware1,
返回了next(action),

也就相当于
middleware2(middleware3((store.dispatch)))(action),

执行完后返回next(action)
就相当于middleware3((store.dispatch))(action),

执行完后返回next(action)
就相当于store.dispatch(action)

整个过程我们已经弄清楚了,applyMiddleware中间件的执行过程就是不断的next(action),
而只有最后的next才是执行dispatch,之前的next只代表的传递其他中间件,dispatch方法只在最后一个中间件里执行了一次。

个人觉得这个地方结合了compose之后写的比较精彩,而且设计的非常巧妙。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/51699.html

相关文章

  • redux源码总结

    摘要:源码个人觉得后的代码就是,后返回的任然是一个函数,可以接受参数,在后面介绍的代码中有所涉及。源码中的获取对象供后面使用,里的对应着对象里的,方法可以获取,也就是对象树的总数据。 redux介绍 redux给我们暴露了这几个方法 { createStore, combineReducers, bindActionCreators, applyMiddleware, c...

    高璐 评论0 收藏0
  • 深入redux技术栈

    摘要:另外,内置的函数在经过一系列校验后,触发,之后被更改,之后依次调用监听,完成整个状态树的更新。总而言之,遵守这套规范并不是强制性的,但是项目一旦稍微复杂一些,这样做的好处就可以充分彰显出来。 这一篇是接上一篇react进阶漫谈的第二篇,这一篇主要分析redux的思想和应用,同样参考了网络上的大量资料,但代码同样都是自己尝试实践所得,在这里分享出来,仅供一起学习(上一篇地址:个人博客/s...

    imingyu 评论0 收藏0
  • 深入redux技术栈

    摘要:另外,内置的函数在经过一系列校验后,触发,之后被更改,之后依次调用监听,完成整个状态树的更新。总而言之,遵守这套规范并不是强制性的,但是项目一旦稍微复杂一些,这样做的好处就可以充分彰显出来。 这一篇是接上一篇react进阶漫谈的第二篇,这一篇主要分析redux的思想和应用,同样参考了网络上的大量资料,但代码同样都是自己尝试实践所得,在这里分享出来,仅供一起学习(上一篇地址:个人博客/s...

    VPointer 评论0 收藏0
  • [源码]高性能和可扩展的React-Redux

    摘要:到主菜了,先看它的一看,我们应该有个猜测,这货是个高阶函数。可能有点绕,但就是这么一个个高阶函数组成的,后面会详细说。定义了一个处理函数和高阶函数执行次的方法,这个方法比上面的复杂在于它需要检测参数是否订阅了。 注意:文章很长,只想了解逻辑而不深入的,可以直接跳到总结部分。 初识 首先,从它暴露对外的API开始 ReactReduxContext /* 提供了 React.creat...

    shuibo 评论0 收藏0

发表评论

0条评论

worldligang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<