Ngrx 中 Store 的学习

有一段时间没有更新自己的 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中间件做缓冲。

转载于:https://my.oschina.net/hyzccc/blog/2980491

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值