有一段时间没有更新自己的 blog了,哭(⊙﹏⊙)。其实这段时间接触了很多新知识,只是碎片化地写在了笔记里,但是慢慢久了,就越积越多,现在会找时间,慢慢整理一下,这会有助于我对这些知识的理解。今天整理的是关于 ngrx 中 store 的知识。接触到 store 也是因为项目中放弃了 service 调用 API 的方法,直接用上了 ngrx 中的 store ,不然作为一名菜鸟,还不知道有这么个状态管理的神器。
什么是 Store?
- store:代表数据模型,内部维护了一个state变量,用来描述应用的状态,也可以说是一个传统的数据库。核心方法:
- getState(): 用来获取 store 的状态
- dispatch(): 用来修改store的状态;
- subscribe():用来监听 store 的状态
- action:对行为(如用户行为)的抽象,用 type 字段来表示这个行为的类型;我们可以理解为是用户向 store 传递的信号;
- reducers:reducer 是一个函数(previousState, action) => newState。用来执行根据指定 action 来更新 state 的逻辑。
所以每个围绕Store构建的应用程序都有reducers、actions和单独的应用 store,现在仔细说说其中需要注意的几点。
组合 reducer
当我们有多个reducer的时候,redux会帮我们把他们全部都组合起来;假设我们有两个reducer:
var userReducer = function(state = {}, action) {
switch (action.type){
case'SAY_SOMETHING':
return{
...state,
message:action.value
}
//etc
default:
return state;
}
}
var itemsReducer = function(state = [], action) {
switch(action.type) {
case'DO_SOMETHING':
return{
...state,
//...
}
//etc
default:
return state;
}
}
这里我们需要关注的是两个reducer的初始状态,一个是[],一个是{},这清楚的表明了一个reducer可以处理各种类型的数据结构,这取决于哪一种数据结构符合我们的需求。使用这种多 reducer 的方法,最终会使每个reducer只处理应用状态的一部分;但是我们知道,createStore 要求只有一个 reducer方法。所以我们怎样合并我们的 reducer,还有我们怎样告诉 Redux 每个 reducer 仅仅处理状态的一部分?
这很简单啦,我们使用redux里的 combineReducer,它采用hash,并返回一个方法,当被触发时,会调用所有的 reducers,获得新的状态的一部分,并在 Redux 的状态 Object 里面重新组合他们。长话短说,这里展示了怎样创建带有多个 reducer 的 Redux 实例;
import {createStore,combineReducers} from 'redux';
var reducer = combineReducers({
user:userReducer,
items:sitemReducer
})
var store_0 = ctreateStore(reducer);
console.log('store_0.getState());//{user{},items[]}
所以最后的状态实际上是一个由 userReducer 部分和 itemsReducer 部分组合起来的简单的 hash;
异步 action 里使用中间件 middleware
先假设有一个这种场景:
- 用户点击按钮“Say Hi in 2 seconds";
- 当按钮A被点击后,在释放2s后显示”hi"
- 2s后,消息变成“hi"
很显然这个消息是我们应用状态的一部分,所以我们可以将它保存在 redux store 里面,但是我们想要的是在 action creator 被调用后,我们的 store 只保存信息2s;(因为如果我们立即更新我们的状态,任何状态修改的订阅者,比如view,将会马上知道了,然后在2s后也会立马更新而做出反应)
如果我们像现在这样调用一个action creator
import {createStore, combineReducers} from 'redux';
var reducer = combineReducers({
speaker:function(state={},action){
console.log('speaker was called with state',state,'abd action' , action);
switch(action.type){
case "SAY':
return {
...state;
message:action.message
}
default:
return state;
}
}
})
var store_0= createStore(reducer);
var sayActionCreator = function(message){
return
{
type:'SAY',
message
}
}
console.log(new Date());
console.log(store_0.getState());// {speaker:{message:'hi'}}
我们很快就看到了store状态的更新; 我们想要的action creator更像是这样的:
var asyncSayFunctionCreator_0 = function(message) {
setTimeOut(function() {
return {
type:'SAY',
message
}
},2000)
}
这样的结果是 action creator 不会返回一个action,而是一个 underfined,所以这不是我们想要的结果; 这里有一个技巧:我们将返回一个函数,而不是一个操作。这个函数会在适当的时候调度 action, 但如果我们想让我们的函数能够调度 action,使用dispatch,它应该是这样的:
var asyncSayFunctionCreator_1 = function(message){
return function(dispatch){
setTimeOut(function() {
dispatch {
type:'SAY',
message
}
},2000)
}
}
我们运行上面的例子:
store_0_dispatch(asyncSayFunctionCreator_1('hi'));
它报错了:
Error: Invariant Violation: Actions must be plain objects. Use custom middleware for async actions.Redux给另一个提示: Use custom middleware for async actions.
怎么办?
什么是middleware?
要解决上面的问题,先说一下中间件 middleware,一般来说,中间件是在应用程序A和B部分之间转换A发送给B内容之前的东西。
不是:A ----> B,而是:A ---> middleware 1 ---> middleware 2 --->middleware 3 --->...--->B
从上面看来来我们的函数从我们的异步动作创建者返回不能由 Redux 本地处理,但如果我们在action creator 和reducers中间有中间件,我们可以将这个函数转换成适合 Redux 的东西:action ---> dispatcher ---> middleware 1 ---> middleware 2 ---> reducers 每次调度一个动作(或其他任何东西,比如异步动作创建者案例中的一个函数)时,都会调用我们的中间件,它可以帮助action creator,调遣它想要的真正的动作;
var anyMiddleware = function({dispatch,getState}){
return function(next){
return function(action){
// your middleware-specfic code goes here
}}}
正如我们看到的,一个middleware由三个嵌套的函数构成(依次调用)
- 第一级提供 dispatch 和 getState 函数;
- 第二个级别提供了next,将转换后地输入传递给下一个中间件或Redux(以便Redux最终可以调用所有的reducers
- 第三层提供从上一个中间件或您的调度接受到的 action,并且可以出发向下一个middleware(让action流动)或以适当地方式处理操作;
我们为异步action创建的中间件成为thunk middleware,代码像这样:
var thunkMiddleware = function({dispatch,getState}) {
return function(next) {
return function(action) {
return typeof action === 'function'? action(dispatch,getState):next(action)
}
}
}
applyMiddleware将所有的middleware作为参数,返回一个将要被redux createStore调用的函数,当最后的函数被触发时,它会产生一个更高阶的store,他将middleware应用于store的dispatch; 这样,你可以将middleware加进你的Redux的store中;
import {createStore,combineReducers,applyMIddleware} from 'redux';
const finalCreateStore = applyMiddleware(thunkMiddleware)(createStore);
var reducer = combineReducers ({
speaker: function( state = {}, action){
switch (action.type) {
case 'SAY':
return {
...state,
message: action.message
}
default:
return state
}
}
})
const store_0 = finalCreateStore(reducer);
var asyncSayActionCreator_1 = function (message) {
return function (dispatch) {
setTimeout(function () {
console.log(new Date(), 'Dispatch action now:')
dispatch({
type: 'SAY',
message
})}, 2000)}}
console.log("\n", new Date(), 'Running our async action creator:', "\n")
store_0.dispatch(asyncSayActionCreator_1('Hi'))
小结
一个简单的 store 的数据流可以描述成:
- 捕获用户行为:比如点击事件;
- 将事件发送到根组件;
- 通过dispatch 将action 派发给reducer;
- store反应:返回新的store;
- 在UI上面展示状态变化; 如果有多个 reducer, 可以用 combineReducers 将reducers合并成一个;如果要异步action,可以使用 middleware中间件做缓冲。