资讯专栏INFORMATION COLUMN

redux 闲谈

levius / 2376人阅读

摘要:使用中间件后的也是通过中间件包装后的。在的位置则进行触发监听器,监听器设置则在中增加。现在来解释,和对应关系我当时回答的是对应的函数名称,如果一致的话就会执行。呵呵,那个涂鸦大工程师有问题了如何绑定的呢,怎么确定是而不是。

redux 闲谈
起因: 在与涂鸦智能一个web工程师交流过程中,他询问我dispatch一个action,是如何和reducer 绑定的,dispatch(actionA)只会触发reducerA却不会去触发reducerB.

Github https://github.com/reduxjs/redux

redux 数据流程

redux 遵循严格的单向数据流,以React为例如下图:

(网图,侵删)

通过用户在ViewUI 进行一个dispatch(action);

Store内部自动通过如下形式Reducer(prevState, action)调用

Reducer返回新的State(newState), state变化后调用Store上的监听器(store.subscribe(listener))

在listener内部可以通过 store.getState() 方式得到最新的state进行数据操作

初始化

redux 的 Store 初始化通过 createStore 方法来进行初始化

const store = createStore(combineReducers, prevState, compose(applyMiddleware(...middleware)))

combineReducers 合并后的reducer,reducer 形式如下

function authReducer(state, action) {

    switch(action.type) {
        case "login":
            return { ...state, isLogin: true }
        default:
            return {...state}
    }
}

function userReducer(state, action) {
    // ...如上
}

通过使用combineReducers({ authReducer, userReducer }) 返回一个reducers

prevState 则为reducer 中state 的初始化默认值,这里默认值为整个状态树的默认值

middleware 则为redux 中间件, 增强redux 功能

该部分初始化流程阶段,在下面applyMiddleware 会再次调用
combineReducers 合并 reducer

在上面说到我们可能存在多个reducer,也可能分模块来处理不同的状态问题,这里就需要合并不同模块的reducer,实现代码:

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 (typeof reducers[key] === "function") {
      finalReducers[key] = reducers[key] // ①
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)
  
  // 其他代码...
  
  return function combination(state = {}, action) { // ②
    // 其他代码...
    
    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
  }
}

传入一个reducers = { authReducer, userReducer }, 很明显对reducers 进行了对象遍历,在①这个位置进行了单个reducer函数的拷贝,在②这个位置redux 内部自己创建了一个reducer函数为combination, 在是③这个位置,进行了开发者定义的reducer定义,也就是说dispatch(action) -> combination(state, action) -> customReducer(state, action), 在循环内部每次获取对应的module 的state值作为 previousStateForKey, 传入用户的reducer中,所以用户dispatch的 action中type是和reducer对应的位置在于用户自己的判断

compose 和 applyMiddleware compose

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)))
}

传入一个不定量函数作为参数,主要作为函数从一个数组形式例如[login, fetchToken, fetchUser]这样的函数传入,得到则是fetchUser(fetchToken(login(...args))) 这样的形式, 将函数组合起来,并从右到左,而且最右侧函数可以接受多个参数

示例仅仅为了说明,不在实际业务中出现
applyMiddleware

根据函数字面意思该函数为应用redux 中间件,核心代码如下:

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => { // 这里createStore 通过初始化时候传入
    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."
      )
    }

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

    return {
      ...store,
      dispatch
    }
  }
}

示例logger

const logger = store => next => action => { // example-①
  console.log("dispatching", action)
  let result = next(action)
  console.log("next state", store.getState())
  return result
}

// usage: applyMiddleware(logger)

这里applyMiddleware 接受不定量的redux中间件,我们就来解释一下example-①这里申明是哪里来的。

applyMiddleware 源码中,传入middlewares后,在 ① 的位置就行了第一个次的中间件调用传入middlewareAPI,分别为getStatedispatch 两个方法这里对应 example-① 中的 store,在 ② 的位置进行了上文说的compose调用,把所有的中间件进行了组合,从右到左的调用,此刻传入dispatch 方法,这里方法对应 example-① 中的 next,在上文中说到 compose 对 函数进行了组合,我们这里将store.dispatch 传入当成参数,返回一个新的函数等价于我们在ViewUI 中dispatch的时候其实使用的 compose(...chain)(store.dispatch)(action) 这样的方式,所以在 example-① 中action 是开发者的action。使用中间件后的store.dispatch也是通过中间件包装后的dispatch。在最后 applyMiddleware 把dispatch 返回。

这里有点晦涩难懂在于compose(...chain)(store.dispatch)(action), 这里可以这样理解,每次dispatch的时候,中间件都是会执行一次,传入顺序是[logger, crashReport], 执行顺序为 crashReport -> logger, 自右向左执行。在每个中间件执行过程中都需要返回next(action) 将当前action 继续传递下去

其他参考: redux applyMiddleware 解析

dispatch(action)

下面说到dispatch,这是我们经常用的,如下代码:

function dispatch(action) {
    // 其他代码

    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
  }

这里先说一下 isDispatching 作用, isDispatching 执行reducer 判断

isDispatching really needed?

这里这个参数解决情况如下:

var store = createStore((state={}, action) => {
  if (something) {
    store.dispatch({type: "ANOTHER_ACTION"})
  }
  return state
})

继续下面来说在 ① 的位置执行 currentReducer, 这里reducer 为我们通过 createStore 传入combineReducers, 把对应的currentState 和 action传入, currentState 也是在初始阶段传入的 preloadState。在 ② 的位置则进行触发监听器, 监听器设置则在 store.subscribe 中增加。

现在来解释,action 和 reducer 对应关系

我当时回答的是: action.type 对应 reducer 的函数名称,如果一致的话就会执行。呵呵,那个涂鸦大工程师有问题了如何绑定的呢,怎么确定是reducerA 而不是B。呵呵呵呵呵。NB

后续

还问我 redux-saga、redux-thunk 异步处理方案,我提了一下redux-thunk,后续继续更新。

PS:真应该提升整个沟通流程质量

1.参考地址: https://redux.js.org

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

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

相关文章

  • 闲谈异步调用“扁平”化

    摘要:而的主线程中不允许操作网络,更是将程序员们推向了异步的深渊。异步深渊产生的主要原因是回调,这在里尤其严重。为了逃离回调的深渊,大家开始想各种办法来把回调扁平化。目前已经在等环境中得到支持,使用的不仅能大大简化代码,还能降低逻辑思路的复杂度。 哦,代码……就把它们当成插图吧 随着 CPU 从单核变多核,软件从注重功能到注重体验,Web 从页面跳转方式到 Web2.0 的无刷新加载(AJA...

    Jaden 评论0 收藏0
  • Node闲谈之Buffer

    摘要:闲谈系列不涉及具体的讲解,只会勾勾画画一些自己认为比较重要的特性。我们一般认为用两个字节位表示,并且完全囊括了字符集。将其转换成进制就是只是表示它们是码。三的读取和写入相关重要的只有能够读写,才能够显示其存在的价值。 原文地址:http://www.cnblogs.com/DeanCh... 在刚接触Nodejs的时候,有些概念总让学前端的我感到困惑(虽然大学的时候也是在搞后端,世界上...

    Godtoy 评论0 收藏0
  • 2016年末闲谈iOS开发的未来

    摘要:已经得到了广大开发者的一致认可。移动市场已经饱和年苹果发布第一个,同年年末安卓发布。从今年手机的出货量和身边的观察很容易得到这样的结论移动开发这块蛋糕的高速增长已经结束了。 showImg(https://segmentfault.com/img/bVEUH9?w=1240&h=518); 移动开发市场潮流涌动,好多人都会问iOS开发的前景这样的问题,今天我就瞎扯一下我眼中的未来,纯主...

    kid143 评论0 收藏0
  • 2016年末闲谈iOS开发的未来

    摘要:已经得到了广大开发者的一致认可。移动市场已经饱和年苹果发布第一个,同年年末安卓发布。从今年手机的出货量和身边的观察很容易得到这样的结论移动开发这块蛋糕的高速增长已经结束了。 showImg(https://segmentfault.com/img/bVEUH9?w=1240&h=518); 移动开发市场潮流涌动,好多人都会问iOS开发的前景这样的问题,今天我就瞎扯一下我眼中的未来,纯主...

    shiina 评论0 收藏0
  • 2016年末闲谈iOS开发的未来

    摘要:已经得到了广大开发者的一致认可。移动市场已经饱和年苹果发布第一个,同年年末安卓发布。从今年手机的出货量和身边的观察很容易得到这样的结论移动开发这块蛋糕的高速增长已经结束了。 showImg(https://segmentfault.com/img/bVEUH9?w=1240&h=518); 移动开发市场潮流涌动,好多人都会问iOS开发的前景这样的问题,今天我就瞎扯一下我眼中的未来,纯主...

    Wildcard 评论0 收藏0

发表评论

0条评论

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