概念 Redux 作为一个状态管理器,可以应用于多种 web 技术或框架中,React 只是其中之一;Redux 的特点在于,多个页面或组件使用同一个状态(store,用于管理应用的 state),可以实现各模块或组件之间的数据共享,应用的任何部分都能进行状态修改,避免了传统的组件间深层次传值问题;
使用 创建状态(store) Redux.createStore()
方法用于创建一个 store
,其接收 reducer
作为第一个参数;
reducer
为一个自定义函数,接收 state
作为第一个参数,同时返回一个值作为新的 state
;
reduce 有缩减,减少的意思,可以理解为一个缩减器,不断将新得到的状态覆盖原状态,以实现 store
的单一状态更新,其名字也是根据 Array
的 .reduce()
方法而来的;
1 2 3 4 5 6 7 import Redux from 'redux' ;const reducer = (state = 1 , action ) => { return state; } const store = Redux .createStore (reducer);
createStore()
方法还接收第二个参数 **initialState
**,作为 state
的初始化值,即下面两种写法效果相同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import Redux from 'redux' ;const store1 = Redux .createStore ((state = 1 ) => { return state; }); const store2 = Redux .createStore ((state ) => { return state; }, 1 ); const state1 = store1.getState ();const state2 = store2.getState ();console .log (state1); console .log (state2);
读取状态 getState()
,是所创建的 store
对象的一个方法,用于获取创建的状态;
1 2 3 4 5 6 const store = Redux .createStore ( (state = 1 ) => state; ); const state = store.getState ();console .log (state);
改变状态(action) 触发更新 state
的更新需要通过触发 action
来实现,actoin
是前面的 reducer
函数接收的第二个参数,一个 action
是一个包含操作信息的对象,同时也可以携带要传递的额外数据;
触发 action
使用 dispatch
实现,dispatch
是 store
对象的一个方法,其接收参数为 action
对象,是更新状态的唯一途径;
这里之所以多加一层 action
,而不直接修改状态,是为了追踪某一状态为何更新,或者调试时进行操作复现等目的,而 action
中的 type
就相当于为了被追踪而留下的痕迹;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const store = Redux .createStore ( (state=1 , action ) => { if (action.type === 'myAction' ) { return action.myData ; } else { return state; } } ); const action = { type : 'myAction' , myData : 'myContent.' , } store.dispatch (action); console .log (store.getState ());
在模块较多的复杂应用中,为了辨识操作,方便理解,通常 type
的格式会定义为 模块/操作
的形式,模块
一般和对应的 reducer
相关,例如:
1 2 3 4 5 const todoReducer = (state, action ) => state;const userReducer = (state, action ) => state;const addTodo = { type : 'todo/add' };const renameUser = { type : 'user/rename' };
响应更新 更新 state
的 action
被触发了,还需要定义一些操作对其进行响应,在 action
触发时执行,即指定如何更新 state
;
这里更新 state
的逻辑写在之前创建 store
时传入的 reducer
函数中,由于 Redux 中的 state
是只读的(并未强制,但需自行在代码中遵守),所以 reducer
每次返回的 state
都是新的;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const myState = { num : 0 } const myReducer = (state=myState, action ) => { if (action.type === 'add' ) { return { num : state.num + 1 }; } else { return state; } } const store = Redux .createStore (myReducer);const myAction = { type : 'add' } store.dispatch (myAction); console .log (store.getState ().num );
Redux 并未强制 reducer
中的 state
为只读的,其实是可以对其进行修改,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const defaultState = { num : 0 };const store = Redux .createStore ( (state=defaultState, action ) => { if (action.type === 'add' ) { state.num += 1 ; return state; } else { return state; } } ); console .log (store.getState ()); store.dispatch ({ type : 'add' }); console .log (store.getState ());
但官方并不建议这么做,这有可能导致页面数据得不到及时更新的 bug,所以需要开发者考虑自行维护其不可变性(Immutability) ,这也能实现更好的状态追踪,问题追溯等开发体验,如官网提到的一项叫 time traveling debugging
技术;并且,Redux 官网对该框架的介绍也是 Redux is a predictable state container
,即具有预见性的状态管理器;
订阅状态 subscribe()
是 store 对象的方法之一,它接收一个函数作为参数,用于设置监听器以订阅状态的更新,即指定 state
更新时应该做什么;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const store = Redux .createStore ((state=0 , action ) => { if (action.type === 'add' ) { return state + 1 ; } else { return state; } }); store.subscribe (() => { console .log ('dispatch' , store.getState ()); }) store.dispatch ({ type : 'add' }); store.dispatch ({ type : 'add' });
拓展 状态合成 虽然 Redux 为了管理方便而设置单一的 store
对所有 state
进行统一管理,但是状态量的增长会使得书写变得复杂,所以 Redux 对象提供了一个 combineReducers()
方法,将所有声明的分工不同(不同组件、页面或子应用)的 reducer
合并为一个总的 reducer
;
该方法接收一个对象作为参数,不同的属性名用于标识不同作用的 reducer
,以及状态更新后从 store
中取回状态值,属性值为声明的 reducer
函数;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const calcReducer = (state=1 , action ) => { switch (action.type ) { case 'add' : return state + 1 ; case 'minus' : return state - 1 ; default : return state; } } const countReducer = (state=0 , action ) => { if (action.type === 'add' ) return state + 1 ; else return state; } const rootReducer = Redux .combineReducers ({ calc : calcReducer, count : countReducer, }); const store = Redux .createStore (rootReducer);console .log (store.getState ()); store.dispatch ({ type : 'add' }); console .log (store.getState ());
combineReducers()
参数对象中指定的属性名用于存储该 reducer 的所有状态值;
Enhancer Redux.createStore()
方法还可以接收第三个参数 **enhancer
**,用于自定义 store
的功能或强化其能力(例如魔改),比如改变 dispatch()
, getState()
, subscribe()
等方法的默认行为;
enhancer
参数为一个自定义函数,其接收 Redux.createStore
这个方法作为参数,并返回一个新的 createStore
方法;
下面是一个为 dispatch
添加功能的简单示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 const myReducer = (state=1 , action ) => { if (action.type === 'add' ) { return state + 1 ; } else { return state; } } const myEnhancer = (createStore ) => { return (reducer, initialState, enhancer ) => { const store = createStore (reducer, initialState, enhancer); store.dispatch = (action ) => { console .log ('dispatched.' ); return store.dispatch (action); } store.getState = () => { return store.getState () + 1 ; } return store; } } } const store = Redux .createStore (myReducer, undefined , myEnhancer);store.dispatch ({ type : 'add' }); console .log (store.getState ());
需要同时使用多个 enhancer
时,需要进行合成,可以使用 Redux.compose()
方法:
1 2 const enhancers = Redux .compose (enhancer1, enhancer2); const store = Redux .createStore (myReducer, undefined , enhancers);
Middleware 大多数时候,我们只希望自定义 dispatcch
方法的逻辑,所以官方专门提供了一个叫 middleware
的特性,翻译过来就是中间件 ,即在触发 action
和调用 reducer
执行响应之间,给用户提供一个可操作空间,如用于日志记录,问题报告,或者处理异步操作等;
middleware
是一个自定义函数,其接收一个对象作为参数,该参数对象有两个方法,分别是 dispatch
和 getState
,逻辑都与 store
对象中的两个同名方法相同;
middleware
函数还需要返回另一个函数作为包装(自定义)后的 dispatch
方法,由于逻辑层次较多,下面会通过代码说明;
Redux 中内置了一个叫做 applyMiddleware
的 enhancer
方法,用于添加 middleware
,它可以接收多个参数以传入多个 middleware
;
具体实现通过举例说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 const myReducer = (state, action ) => { if (action.type === 'add' ) { return state + 1 ; } else { return state; } } const myMiddleware1 = ({ dispatch, getState } ) => { return (next ) => { return (action ) => { console .log ('mid 1' , getState ()); return next (action); } } } const myMiddleware2 = ({ getState } ) => next => action => { console .log ('mid 2' , getState ()); const result = next (action); console .log ('mid 2 new' , getState ()); return result; } const myEnhancer = Redux .applyMiddleware (myMiddleware1, myMiddleware2);const store = Redux .createStore (myReducer, 1 , myEnhancer);store.dispatch ({ type : 'add' }); console .log (store.getState ());
总结一下整个执行过程就是:
用户调用了 store.dispatch()
触发 action
;
Redux 按 applyMiddleware()
方法中参数的传入顺序,挨个执行自定义的 middleware
逻辑;
然后再调用原始的 store.dispatch()
方法触发 action
;
最终执行 reducer
中的逻辑;
整个过程有些类似函数的链式调用:
1 dispatch -> middleware1 -> middleware2 ... -> dispatch -> reducer
此外,由于 middleware
的执行逻辑,其特性还包括对 action
中数据的修改、中断甚至彻底停止 action
,的触发,例如上例中最后不返回 next(action)
,那么整个过程执行完第一个 middleware
就结束了,state
也不会发生预期的改变;
处理异步逻辑 Redux 内部并不知道如何处理异步逻辑,只会同步的触发 action,然后调用 reducer 更新 state,所以任何异步逻辑需要我们在外部自己实现;而 Redux 的宗旨是 recuder 不要有任何副作用 ,最好是一个纯函数 ,即不要有多余的外部联系,如控制台打印,异步请求等;
而 middleware
就是 Redux 专为副作用 逻辑需求而设计的,这里以异步操作为例用代码进行简单实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 const reducer = (state, action ) => { if (action.type === 'add' ) { return state + 1 ; } else if (action.type === 'asyncAdd' ) { return action.data ; } else { return state; } } const asyncMiddleware = _store => next => action => { if (action.type === 'asyncAdd' ) { setTimeout (() => { action.data = 'some data.' ; next (action); }, 2000 ); } else { next (action); } } const enhancer = Redux .applyMiddleware (asyncMiddleware);const store = Redux .createStore (reducer, 0 , enhancer);store.dispatch ({ type : 'add' }); console .log (store.getState ()); store.dispatch ({ type : 'asyncAdd' }); console .log (store.getState ()); setTimeout (() => { console .log (store.getState ()); }, 2000 );
结果显示异步操作获取的数据,可以成功被 reducer
拿到并实现相应的逻辑,所以把 setTimeout
换成 Ajax
请求也同样可以从服务器获取到数据,然后传递给 Redux 进行下一步处理;
由于上面的异步逻辑的原生写法不太方便,Redux 官方就提供了一款 redux-thunk
工具,封装好了一个 middleware
,应用之后就可以将 action
声明为一个函数(以前是一个对象),其接收 dispatch
和 getState
两个参数;具体用法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import Redux from 'redux' ;import ReduxThunk from 'redux-thunk' ;const reducer = (state, action ) => { if (action.type === 'add' ) { return state + 1 ; } else if (action.type === 'asyncAdd' ) { return action.data ; } else { return state; } } const middlewareEnhancer = Redux .applyMiddleware (ReduxThunk .default );const store = Redux .createStore (reducer, 1 , middlewareEnhancer);const asyncAction = (dispatch, getState ) => { console .log ('old state:' , getState ()); setTimeout (() => { dispatch ({ type : 'asyncAdd' , data : 'some data.' }); }, 2000 ); } store.dispatch (asyncAction); setTimeout (() => { console .log (store.getState ()); }, 2000 );
需要注意 的是,一些教程上(包括 Redux 官网)介绍 Redux Thunk
的用法时,仍然使用的 Redux.applyMiddleware(ReduxThunk)
写法,这是该工具 1.x
版本的写法,现在 2.x
版本需要加上 .default
,即 Redux.applyMiddleware(ReduxThunk.default)
,不然程序会出现问题;