1

Redux 设计思想

  摘录于官网:Redux is a predictable state container for JavaScript apps.(大致意思是:Redux对于JavaScript应用而言是一个可预测状态的容器。通俗来讲,Redux只是一个数据流管理框架)

1、状态管理,应用中应且仅只有一个 store 对象,所有的状态都保存在一个 store 对象里面。

2、视图与状态是一一对应。

3、数据流管理,遵循单向数据流。这意味着您的应用程序数据将遵循单向绑定数据流,也因此应用的状态变化变得可预测。

Redux 三大基本原则:

1、单一数据源。整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。

2、State 是只读的。唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。

3、使用纯函数来执行修改 state。为了描述 action 如何改变 state tree ,你需要编写 reducers (纯函数)。

视图与状态是一一对应

  源码解析前,先简单梳理视图与状态的关系。如上图,整个应用的 State 都保存在一个 Store 对象中,State 和 View(视图)一一对应。也就是说,一个 State 对应一个 View(视图),只要 State 相同,View(视图)也就相同,反之亦然。故此,好处可想而知了。

Redux - Data Flow

1、View 触发 dispatch(aciotn),通知 Store 更新 State 及 通过 subscribe(订阅) 获取新的 State。

2、Store 根据 当前的State 和 dispatch(aciotn) 调用 reducecr 函数进行计算,返回新的 State。

3、Store 通过执行监听(listener)函数来通知 View 更新。

源码目录图解

源码分析

combineReducers

核心函数:运用了闭包

  combineReducers 函数的作用是:把一个由多个不同子 reducer 函数作为 value 的 object,最终合并成一个 reducer 函数,然后就可以对这个 reducer 调用createStore。

  合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象

import ActionTypes from './utils/actionTypes' // redux保留的私有action type
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject' // 判断是否为普通对象
...
...
/**
 * 核心函数:运用了闭包
 * combineReducers函数的作用是:把一个由多个不同子reducer函数作为value的object,最终合并成一个*reducer 函数,然后就可以对这个reducer调用createStore。
 * */
export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers) // //取出reducer的各个key值
  const finalReducers = {} // 作用于闭包函数,确保了 finalReducers 变量不被垃圾回收,combination闭包函数可以读取该变量
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]
    
    if (process.env.NODE_ENV !== 'production') { //  node全局变量,process该对象表示Node所处的当前进程
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }
    
    if (typeof reducers[key] === 'function') { // 过滤无效的reducer,reducer必须是一个function 
      finalReducers[key] = reducers[key] // 存储有效的reducer到定对象finalReducers
    }
  }
  const finalReducerKeys = Object.keys(finalReducers) // 获取有效reducer key
  ...
  // 针对有效 reducer 合法性判断,捕捉异常
  let shapeAssertionError
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }
  return function combination(state = {}, action) {
    // reducer 非法异常抛出
    if (shapeAssertionError) {
      throw shapeAssertionError
    }
   ...
    let hasChanged = false // 判断 state 是否改变过的状态
    const nextState = {}
    //  根据 key 进行更新对应的state
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i] // 索引对应的 key
      const reducer = finalReducers[key] // 根据 key 读取对应的 reducer
      const previousStateForKey = state[key] // 上一次(旧) state
      const nextStateForKey = reducer(previousStateForKey, action) // 当前(新) state,根据 reducer 函数返回的新 state
      // 若 state 返回为 undefined,则进行异常捕捉抛出
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey // 将 reducer 计算返回的新 state 赋值给 nextState 对应的key
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey // nextStateForKey 和 previousStateForKey 浅比较,更新 hasChanged 状态
    }
    
    // 根据 state 是否改变过状态进行判断,返回对应的 state
    return hasChanged ? nextState : state
  }
}

   分析以上 combineReducers 的源码,可以知道其中体现函数式编程思想,函数运行的结果只依赖于输入的参数,而不依赖于外部状态,也就是我们常说的无副作用函数式编程。

   首先,对传入的 reducers 进行无效过滤及浅复制到 finalReducers 对象,确保了 finalReducers 变量不被垃圾回收,combination闭包函数可以读取该变量

   其次,hasChanged 状态更新,nextStateForKey 和 previousStateForKey 浅比较。若 reducer 是非纯函数,在触发 reducer 修改 state 时,直接在旧的 state 修改,也就是不改变 state 的引用地址,那么 hasChanged 将不会进行更新,因为 nextStateForKey !== previousStateForKey 为浅复制。因此要求 reducer 必须是纯函数,不可修改输入值 state。

createStore

   CreateStore方法作用是创建一个 Redux store 来以存放应用中所有的 state。 改变store中数据的唯一方法是调用store的dispatch()方法。

   应用中应有且仅有一个 store。为了响应 store 树中不同的部分 state,可以通过combineReducers方法将多个reducer组合成一个reducer,最终形成一个与我们的数据相对应的 reducer 树。

...
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
/**
 * @param {Function} reducer 接受两个参数,分别是当前的 state 树和要处理的 action, 返回新的 state 树
 * 
 * @param {any} [preloadedState] 初始化的 state。在同构应用中,你可以决定是否吧服务端的传来的 state 水合(hydrate)后传给它,
 * 或者从之前保存的用户会话中恢复传给它。如果你是使用 combineReducers 创建 reducer,那么它必须也是一个普通对象,与传入的 key 保持同样的结构。
 * 否则,你可以自由传入 reducer 可理解的内容。
 * 
 * @param {Function} [enhancer] Store enhancer 是一个组合 store create 的高阶函数,返回一个新的强化过的 store creator。这与 middleware 相似,
 * 它允许你通过复合函数改变 store 接口。
 *
 * @returns {Store} 保存了应用所有 state 的对象。改变 state 的唯一方法是 dispatch action 并 subscribe 监听 state 的变化,然后更新 UI.
 */
 export default function createStore(reducer, preloadedState, enhancer) {
  ...
  let currentReducer = reducer // 当前的 reducer
  let currentState = preloadedState // 当前的 State
  let currentListeners = [] // 当前的 监听(listeners)函数列表
  let nextListeners = currentListeners // 新生成的监听(listeners)函数列表
  let isDispatching = false // dispatch的标记锁,作用:判断是否正在执行 dispatch
  
  //  确保 nextListeners 与 currentListeners 保持一致,且保证 nextListeners 的变化不影响当前 currentListeners
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }
  **
   * @returns {any} 获取当前 state 树
   */
  function getState() {
    // 正在执行 dispatch 中,不能调用 store.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 内部执行,每当dispatch(action)后所有监听函数都会被触发一次
  function subscribe(listener) {
    ...
    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)
      currentListeners = null
    }
  }
  /**
   * 核心函数:触发 state 改变的唯一方法
   * 当发送 dispatch(aciton) 时,用于创建的 store 的 ‘reducer(纯函数)’ 都会被调用一次。调用的传入的参数是当前的 state 和发送 ‘action’,
   * 调用完成后,所有的 state 监听函数都会触发。
   */
  function dispatch(action) {
    ...
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    // 调用 reducer 函数更改 state 完成后, 执行监听函数
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
    return action
  }
  
  /**
   * 作用:替换 store 当前使用的 reducer 函数
   * 场景:比如需要动态加载某些 reducer
  */
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }
    currentReducer = nextReducer
    dispatch({ type: ActionTypes.REPLACE })
  }
  ...
  // store创建好以后,立即发送一个初始化action。这样做是为了让 reducer 返回store的初始状态(当给reducer传入的当前state为undefined时,reducer 会返回store的初始状态)
  dispatch({ type: ActionTypes.INIT })
  ...
}

getState方法

   获取当前 state 树,且正在执行 dispatch 中,不能调用 store.getState() 抛错拦截

subscribe方法

  新增一个变更监听函数且返回一个解绑函数,作用于 dispatch 内部执行,每当 dispatch(action) 后所有监听函数都会被触发一次

dispatch方法

  核心函数:触发 state 改变的唯一方法。

  当发送 dispatch(aciton) 时,用于创建的 store 的 ‘reducer(纯函数)’ 都会被调用一次。调用的传入的参数是当前的 state 和发送 ‘action’,调用完成后,所有的 state 监听函数都会触发

replaceReducer方法

  作用:替换 store 当前使用的 reducer 函数

  场景:比如需要动态加载某些 reducer

初始化action

   store创建好以后,立即发送一个初始化action。这样做是为了让 reducer 返回store的初始状态(当给reducer传入的当前state为undefined时,reducer 会返回store的初始状态)

dispatch({ type: ActionTypes.INIT })


-


alita
3 声望0 粉丝