前言
redux是个状态管理工具,提供简化的api做到行为的可预测性,由于react在组件之间通信繁琐,状态管理需要状态提升造成代码冗长等局限,redux常与react结合使用,但是redux作为独立的框架,其实是可以和其他的库配合使用的,为了不被react混淆了redux的使用方法,我们这里结合jq来理解redux的使用
redux基本概念
redux有三个基本原则
- 单一数据源:一个应用state只存在唯一一个对象树当中(state状态与试图层级对应形成树装结构,所以称为对象树),由唯一的store统一管理
- state是只读的:redux规定不能直接修改state,修改state需要触发action,action* 是一个包含type字段的普通函数,用来描述发生的事情
- 使用纯函数修改state:通过action描述怎样修改state,需要手动创建函数reducer(一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数)
const state ={
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: 'Consider using Redux',
completed: true,
},
{
text: 'Keep all state in a single tree',
completed: false
}
]
}
我们可以根据之前定义的action更新state,创建redux
const state ={
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: 'Consider using Redux',
completed: true,
},
{
text: 'Keep all state in a single tree',
completed: false
}
]
}
function reducer(state=state,action){
switch(action.type){
case 'ADD_TODO':
return Object.assign({},state,{
todos:[
...state.todos,
{text:action.text,completed:false}
]
});
case 'SET_VISIBILITY':
return Object.assign({},state,{
visibilityFilter:action.filter
});
default:
return state
}
}
可以看到上面的两个更新是可以独立拆分出来的,分成两个小的reducer,最后合成出一个完整的state
function reducer(state=state,action){
return {
todos:todos(state.todos,action),
visibilityFilter:visibilityFilter(state.visibilityFilter,action)
}
}
function todos(state=[],action) {
switch(action.type){
case 'ADD_TODO':
return [
...state,
{
text:action.text,
completed:false
}
]
default:
return state
}
}
function visibilityFilter(state='SHOW_ALL',action){
switch(action.type){
case 'SET_VISIBILITY_FILTER':
return action.filter
default:
return state
}
}
redux本身提供了一个方法combineReducers(reducers),辅助我们根据给出的key值让拆分出来的函数管理对应的state,返回一个完整的reducer,合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。
const reducer = combineReducers({
todos,
visibilityFilter
})
下面是combineReducers部分源码
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)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
通过上面的源码可以看到,在调用combineReducers方法后返回一个函数combination,这个combination就作为接下来要说的createStore的第一个必填参数传入,生成store
createStore
cosnt store = createStore(reducer);
createStore接收三个参数,reducers、initState、enhancer(中间件)
createStore最重要的是在内部构造了四个方法,分别是:
dispatch: 接收action,更新状态数。
getState: 得到当前最新state树。
replaceReducer: 替换reducers,初始化state树。
subscribe: 用于观察者模式,设置监听,并返回退出监听方法,在调用dispatch时,运行所有被监听对象。
createStore接受三个参数,第一参数reducers是必填,第二个参数initState用来初始化state,第三个参数用来合并中间件,这里我们分析一下store的四个方法和中间件,initSate初始化状态和服务端渲染没有做过多研究,之后补充。
createStore创建一个单一的store,用来管理唯一state,state是一个对象,store管理这个对象,通过store.getState返回最新的state。
function getState() {
return currentState;
}
redux规定不能直接修改应用的状态 state,想要修改state只能通过dispatch方法,dispatch接受一个叫action的对象,里面必须包含一个 type 字段来声明你到底想干什么。redux相当于生成了一个共享,共享的状态如果可以被任意修改的话,那么程序的行为将非常不可预料,所以我们提高了修改数据的门槛:你必须通过 dispatch 执行某些允许的修改操作,而且必须大张旗鼓的在 action 里面声明。
/**
*通过部分源码可以看到dispatch接受action,然后调用reducers函数放回新的state更新当前的state
*至于怎么写这个reducers有很多方式,不做本文重点,不做介绍,可以看官方文档,至于用哪种方式,怎么用就看个人喜好,用与不用它就在那里不悲不喜
*/
const dispatch = (action)=>{
currentState = reducers(currentState,action);
}
现在可以获取到state并修改它,那我们想每次更新状态的时候做一些统一的操作,比如打印出state看到变化,我们需要调用store.subscribe
nextListeners.push(listener);
return function unsubscribe() {
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
};
通过部分源码我们可以看到,subscribe其实就是接收一个函数,然后把它存在一个数组,每次dispatch调用时遍历一遍这个数组
//dispatch部分源码
for (var i = 0; i < listeners.length; i++) {
listeners[i]();
}
还有一个replaceReducer方法,他接受一个新的reducers替换旧的reducers,然后初始化一个新的state
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer // 就是这么耿直简单粗暴!
dispatch({ type: ActionTypes.INIT }) // 触发生成新的 state 树
}
dispatch与middleware
store里的dispatch函数只能接收一个action然后传给reducer更新state然后重新渲染,想要扩展一些操作就需要中间件来加强dispatch的功能,例如向服务器请求后进行后再进行更新state的操作,可自己定义一个中间件来进行想要的强化,输出不够补输出,防御不够补护甲。
applyMiddleware(...middleware)
applyMiddleware方法接收多个中间件方法,将多个中间方法合成一个新的dispatch,然后执行新的dispatch时根据传入顺序逐个执行
var store = createStore(reducer, preloadedState, enhancer)
var dispatch = store.dispatch // 指向原 dispatch
var chain = [] // 存储中间件的数组
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI)) //生成一个中间的数组
dispatch = compose(...chain)(store.dispatch)
return {
...store, // store 的 API 中保留 getState / subsribe / replaceReducer
dispatch // 新 dispatch 覆盖原 dispatch,往后调用 dispatch 就会触发 chain 内的中间件链式串联执行
}
在上面代码中最主要的就是compose函数,它将个所有的中间件合并然后生成新的增强后dispatch,就像dispatch加了一层又一层的buff,为什么是说它是一层一层的是因为compose将所有的中间件组合生成新的dispatch就是洋葱一样将dispatch一层层包裹传递
function compose(...funcs) {
return arg => funcs.reduceRight((composed, f) => f(composed), arg);
}
我们调用compose函数将chain里的函数从右向左依次执行,传入的arg就是store.dispatch,假设我们添加了三个中间件最后生成的dispatch
dispatch = f1(f2(f3(store.dispatch))))
就像洋葱一样将dispatch一层层包裹,首先执行f3然后将f3中间件的返回值是一个函数作为参数传给传给f2,f2执行后又包裹了一层f2的返回值,然后传给f1,最后f1将自己的返回值与之前两个中间件的返回值组一起生成新的dispatch函数
我们用一个简单的logger中间件理解一下这个过程
const logger = (store)=>(next)=>(action)=>{
console.log(store.getState());
next(action);
console.log(store.getState());
}
之前在applyMiddleware有这样一行代码
chain = middlewares.map(middleware => middleware(middlewareAPI))
它生出一个中间件的数组集合的同事其实也执行了每个中间件,所以中间函数到下一层返回了一个函数
(next)=>(action)=>{
console.log(store.getState());
next(action);
console.log(store.getState());
}
而到了compose合并的时候返回的的新函数就是
(action)=>{
console.log(store.getState());
next(action);
console.log(store.getState());
}
理解这个过程很重要,这里执行dispatch没有直接调用store里的dispatch而是传进去,是因为如果是多个中间件调用的话,这里的next可能是上一层的(action)=>{}函数不是store.dispatch
我们在加一个logNext的函数将next打印出来看一下效果
const logNext = (store)=>(next)=>(action)=>{
console.log(next.toString());
next(action);
}
我们可以看到此时的next
function (n){console.log(e.getState()),t(n),console.log(e.getState())}
它的运行顺序
--------------------------------------
| middleware1 |
| ---------------------------- |
| | middleware2 | |
| | ------------------- | |
| | | middleware3 | | |
| | | | | |
next next next ——————————— | | |
dispatch —————————————> | reducer | | | |
nextState <————————————— | | | | |
| | | ——————————— | | |
| | | | | |
| | ------------------- | |
| ---------------------------- |
--------------------------------------
在生成新的dispatch是层层包裹。层层进入,层层冒出,就像是剥洋葱
总结
本文介绍了redux的一些机制,它可以帮助我们管理状态,并且通过准守它的规定,可以让状态的修改可预料,并且实现了自动渲染,redux是优秀的函数式编程应用,体现了函数式编程的灵活性,可以根据自己的需求自定义扩展,来管理使用。