Flux架构
Redux是基于Facebook提出的Flux架构设计出来的。Flux不是一个框架或者库,可以认为Redux是Flux的一种实现形式。Flux架构强调数据应该是单向的数据流。
Flux架构将应用拆分成四个部分:
- View(视图层):UI界面
- Action(动作):View会触发的一系列事件动作
- Dispatcher(分发器):对Action进行分发
- Store(数据层):存储应用的数据状态,store的变化最终会映射到View上。
单向数据流的优势在于:对于数据装填的变化是可预测的。如果store中的状态发生了变化,那么一定是因为dispatch了某个action。所以在Redux官网映入眼帘的就是:
Redux:A Predictable State Container for JS Apps
了解了Flux架构之后再来看Redux就会更容易理解。
Redux核心原理
Redux核心组成有三部分:
- Store:存储数据状态的容器
- Action:动作
- Reducer:一个函数,接受两个参数,第一个参数是当前的state,第二个参数是action。reducer负责根据action对状态进行处理。为什么叫Reducer,是因为Reducer函数可以作为Array的reduce函数的参数。
Flux的dispatch哪去了?Redux并不是严格遵循Flux架构设计的,dispatch在Redux中被整合到了Store中。
Redux整个工作过程中,数据流是严格单向的,只能通过dispatch action 的方式触发数据状态的修改。Action会进入对应的Reducer进行处理最终得到新的状态State,然后进一步的触发View的数据更新。
我们使用Redux最重要的一步就是通过createStore创建一个store:
const store = createStore(reducer, initState, applyMiddleware(Middleware1, Middleware2...))
createStore
接受三个参数:
- reducer
- 初始状态initState
- enhancer对createStore能力进行增强的函数,如applyMiddleware,添加一些中间件
具体做了什么事情?下面我简化了createStore
方法的逻辑(去掉了边界case相关的代码)并对每一步进行了注释:
export default function createStore(reducer, preloadedState) {
// 保存reducer的变量
let currentReducer = reducer
// 保存state的变量
let currentState = preloadedState
// 订阅状态的改变,在state改变之后会触发里面的监听事件
let currentListeners = []
// 获取当前的state
function getState() {
return currentState
}
// 订阅函数
function subscribe(listener: () => void) {
let isSubscribed = true
// 将订阅函数放到listeners队列中,state更新后会一次调用里面的函数
currentListeners.push(listener)
// 返回一个取消订阅的函数
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
const index = currentListeners.indexOf(listener)
currentListeners.splice(index, 1)
}
}
// 分发action的函数
function dispatch(action) {
// 执行reducer,根据action生成新的state保存到currentState
currentState = currentReducer(currentState, action)
const listeners = currentListeners
// 依次触发listeners中订阅的函数,更新UI
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
// 这里触发一个dispatch,不会命中reducer里面的任何逻辑,
// 所以直接走default,返回初始的state,达到设置初始默认值的目的
dispatch({ type: ActionTypes.INIT })
// 闭包
const store = {
dispatch: dispatch,
subscribe,
getState,
// ...
}
return store
}
redux中间件
redux中间件使用方式是通过 applyMiddleware
方法,applyMiddleware
接受任意个数个中间件作为参数,在一开始介绍createStore
的时候用到了applyMiddleware
方法。
applyMiddleware函数实际的作用是对store的dispatch方法进行增强。下面对applyMiddleware方法每一步进行了注释:
export default function applyMiddleware(
...middlewares
) {
// 返回一个函数,接受参数是createStore方法。
return (createStore) => <S, A extends AnyAction>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>
) => {
// 调用createStore,创建一个store
const store = createStore(reducer, preloadedState)
let dispatch: Dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
// middleware接受的参数,一个middleware实际上就是一个函数
// 参数包含两个属性,getState和dispatch,所以一个redux的中间件需要接受并使用这两个方法
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
// 遍历middleware数组,给middleware数组传递上面的middlewareAPI参数,得到一个新的函数数组
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 得到一个新的dispatch方法,替换原有store的dispatch方法。
// 新的dispatch方法通过compose方法将上一步得到的函数数组组合成一个函数,具体如何做到的,下面会描述。
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
// 返回一个新的store对象,dispatch是通过compose函数得到的新的dispatch方法
return {
...store,
dispatch
}
}
}
业务代码中调用的dispatch
实际上会将传过来的action经过各个Middleware处理后调用createStore()
返回的真正的dispatch
。
applyMiddleware
最终最需要搞清楚的就是compose
函数是如何将middleware
函数数组组合成一个函数的?函数合成(compose
)并不是Redux的专利,它是函数式编程的一个概念,在Koa中也可以看到一个compose函数是相同的事情。compose函数的代码非常精简:
export default function compose(...funcs: Function[]) {
// 处理数组为空的边界case
if (funcs.length === 0) {
return (arg) => arg
}
// 处理数组为1的情况,这种情况下也不需要组合,直接返回第一个元素就行
if (funcs.length === 1) {
return funcs[0]
}
// 有多个函数,通过 array的reduce方法来组合
return funcs.reduce((a, b) => (...args: any) => a(b(...args)))
}
如果你还不了解reduce方法,那么这是一个绝好的机会去真正认识到reduce函数的威力。reduce函数的特点就是会执行reduce函数参数的逻辑,将数组中的数组组合成一个结果。
假设我们执行compose(f1,f2,f3)
,得到的结果就是:
(...args) => f1(f2(f3(...args)))
通过compose
函数,我们就可以将多个函数整合成一个函数。
那redux的中间件又是一个什么结构呢?
这里我们找一个redux最常见的中间件redux-thunk
,redux-thunk
是redux中经典的一步action解决方案。redux-thunk
非常简洁,只有几行代码:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
// 当action是一个函数的时候,调用这个函数,传递dispatch、getState给action
// 在action函数中去处理异步逻辑,调用dispatch
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// 如果不是函数,就是一个正常的同步action,直接dispatch
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
看到createThunkMiddleware
函数的返回函数接收的参数是不是非常熟悉?这里就是 applyMiddleware
中向middleware
传递的middlewareAPI
。
一个redux middleware的结构就是这样的:
({ dispatch, getState }) => (next) => (action) => { /* */ }
compose函数最终会将函数合并成:
(...args) => middleware1(middleware2(middleware3(args)))
在applyMiddleware中给compose合成的函数传递的参数是 store.dispatch
,所以({ dispatch, getState }) => (next) => (action) => { /* */ }
中的这个next参数就是store.dispatch
。所以中间件最终会调用到store的dispatch
,完成action
的分发,中间件的作用是对dispatch
的能力进行增强(最终还是要靠dispatch
方法),比如redux-thunk
使得dispatch
可以处理异步逻辑。
当然你可能并不需要Redux
任何技术设计、思想框架,与其说他们的优缺点,不如用“适合不适合”某类场景来表达更加准确、客观。
作为Redux的creators之一的Dan Abramov在很久之前也说过 “You might not need Redux”。Redux的官网中也有关于 When should I use Redux?的版块。对于一个拥有复杂状态更新频繁的App使用redux可能会带来一些维护上的优势,但是对于轻量简单的应用来讲,Redux的使用就不是那么必要的了。使用Redux,开发者必须要写很多模板代码,这种重复和繁琐可能不是开发者所能忍受的。在React中,React hooks的出现可能是对redux的一次降维打击,对于是否使用Redux,还是需要根据具体业务场景进行一次"trade-off"。
总结
本文主要介绍了Redux的核心原理。如果你觉得本文有帮助,还请点赞关注支持一下~