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,