redux

Redux 让state的变化变得可预测

如果把react视为一个状态机,redux就是是用来管理状态的容器,它的理念是传入某个action,永远返回对应的state,类似于纯函数。
redux有三大核心点

  • action 纯对象,标识如何修改state
  • reducer 纯函数,接受action 并返回对应的state
  • store 调度中心,连接action与reducer

API

  • createStore
  • combineReducers
  • bindActionCreators
  • applyMiddleware
  • compose

createStore

createStore(reducer, [preloadedState], enhancer)

preloadedState 可选 设置初始state 如果第二个参数是函数 且未传第三个参数 则认为二个参数是enhancer

几个重要的变量

  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false  //标识是否正在处理某个action
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

这个函数用来拷贝当前的listener堆栈。避免间接修改当前的堆栈,具体作用下面可以见到

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

获取state, 可以看到当前state是存在currentState变量里

订阅

function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }

    if (isDispatching) {
      throw new Error(
        'You may not call store.subscribe() while the reducer is executing. ' +
          'If you would like to be notified after the store has been updated, subscribe from a ' +
          'component and invoke store.getState() in the callback to access the latest state. ' +
          'See http://redux.js.org/docs/api/Store.html#subscribe for more details.'
      )
    }

    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      if (isDispatching) {
        throw new Error(
          'You may not unsubscribe from a store listener while the reducer is executing. ' +
            'See http://redux.js.org/docs/api/Store.html#subscribe for more details.'
        )
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

这里类似于观察者模式,将传入的listener存入nextListeners,注意这里调用了ensureCanMutateNextListeners,为了避免直接修改了当前的listener缓存堆栈
然后返回一个销毁函数。

dispatch

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将action传入reducer, 然后接收reducer返回的state,可用getState函数获取。
在state处理完以后,遍历监听器堆栈,执行回调函数。

currentListeners = nextListeners 获取最新的监听器堆栈 也就是说每次执行dispatch, currentListeners都会等于nextListeners。再回过头来看上面的ensureCanMutateNextListeners函数,可以梳理出这样一个流程,每次subscribe的时候,要确保不直接修改当前的监听器堆栈,而是修改另外一个副本,每次dispatch的时候,从这个副本里获取到最新的堆栈。这是为了避免当前堆栈在执行的时候,新增subscribe影响当前堆栈。所以把subscribe的堆栈与执行所用的堆栈分开。

combineReducers

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

流程
遍历传入的reducers
判断每个reducer是否是一个函数,是就保留
将最终得到reducer进行遍历判断每个reducer在初始情况(state=undefined, action.type为随机值)下能返回正确的state。

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

用这个函数保证了reducer的不会返回undefined的state,但可以为null。

做完这步以后,然会一个rootReducer,这个rootReducer利用闭包保存了之前传入的所有reducer。 这样每次dispatch的时,rootReducer内部会遍历所有的reducer,拿到最新的state,然后按key保存下来。

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)))
}
var c = compose([fn1, fn2, fn3]);
=> fn1(fn2(fn3(1)))

依次执行函数队列,并将上一个函数的返回值作为下个函数的参数

applyMiddleware

为什么需要middleware?

在action被发起到被reducer处理之前,这其中可能会有异步请求或者日志等操作。middleware的作用就是用来拓展从dispatch到reducer这个过程。

实现一个middleware并不难,可以直接修改store的dispatch方法,或者缓存先缓存dispath,然后修改为middleware的方法。

难点在于如何处理多个middleware

假设我们三个middleware

function m1 () {
	console.log('1');
}
function m2 () {
	console.log('2');
}
function m3 () {
	console.log('3');
}

要实现中间件的功能,会想到这样的方法

let middlewares = [m1, m2, m3];
let originDispatch = store.dispatch;
store.dispatch = funcion (action) {
	for (let index = 0; index < middlewares.length; index++) {
		middlewares[index]()
	}
	let ret = originDispatch(action);
	return ret;
}

显然这样是属于硬编码的方式,为了实现可拓展性,需要实现一个applyMiddleware方法。

它应该接受两个参数store和middleware数组(多个中间件的情况)。

applyMiddleware(store, ...middlewares)

首先,store上原来的dispatch方法需要被改写,不然是无法加入中间件的逻辑
然后,这个方法需要能串联多个middleware
并且在调用新的dispatch的时候,首先会调用原store上的dispatch,然后将结果一步步传递下去。

以上就是redux的核心功能

redux的实现方式

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

middleware链

由于应用中可能会包含多个middleware,所以redux中利用compose函数构造了一个middleware链; 梳理一下compose函数的作用

function m1 () {
    console.log('1');
}
function m2 () {
    console.log('2');
}
function m3 () {
    console.log('3');
}
let ret = compose([m1, m2, m3])

在compose函数中第一步:

ret = function () {
    m1(m2(...args))
}

第二步

ret = function () {
    m1(m2(m3(...args)))
}

现在middleware已经可以串联起来了,按从后往前的顺序执行。

接下来的问题是如何把store.dispatch传递下去

柯里化

redux中一个middleware函数如下所示

 function (store) {
     return function (next) {
         return function (action) {
             let ret = next(action);
             console.log('next state', store.getState());
             return ret;
         }
     }
 }

第一步,传入middlewareAPI

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

middleware变为

function (next) {
	return function (action) {
		let ret = next(action);
		console.log('next state', store.getState());
	 	return ret;
	}
}

middleware链运行过程

第一步
m3(store.dispatch)

返回

function m3Result (action) {
	let ret = store.dispatch(action);
	console.log(3)
	console.log('next state', store.getState());
	return ret;
}

第二步

function (m3Result) {
    return function (action) {
        let ret = m3Result(action);
        console.log('next state', store.getState());
        return ret;
    }
}

返回

function m2Result (action) {
	let ret = m3Result(action);
	console.log(2)
	console.log('next state', store.getState());
	return ret;
}

第三步

function (m2Result) {
    return function (action) {
        let ret = m2Result(action);
        console.log('next state', store.getState());
        return ret;
    }
}

返回

function m1Result (action) {
	let ret = m2Result(action);
	console.log(1)
   console.log('next state', store.getState());
   return ret;
}

最终dispatch 函数变为了m1Result,