ngrx 状态管理框架教程

863 阅读4分钟

ngrx 状态管理

本文假设你对 Angualr ,Typescript 有所了解。

不同页面,组件之间如何传递数据?

  1. url 传递
  2. cookie
  3. session
  4. html5 Local storage
  5. lodash
  6. Global object

这些数据根据不同的应用场景可以抽象为不同的状态,以翻页为例,当前页是需要保存的状态,无论是向上翻页还是向下翻页都需要依赖当前的状态。

大型项目系统如何管理状态?

在大型项目系统中,可能会有十几种甚至上百种状态需要保存管理,如果使用 Local storage 或者 lodash ,毋庸置疑是最简单的方式 ,但是那就需要人为管理状态的 key ,并且手动的释放不需要的内存。

设想使用Global Object管理 State

将状态都集中保存到全局的 Js Object 中,开发一种框架对这个 Big Object 进行增删改查的状态操作。

  1. 读写分离

将对 Big Object 的操作,分为读和写两种方式来管理。

(a)写操作称为 Reducer

(b) 读操作称为 Selector

Reducer:对不同的 State 写操作通过用户不同的 Action 触发

Reducer 对象会时刻监听用户触发的 Action ,来判断下一步如何对 State 操作,这里有两个步骤,监听与判断

对于监听 可以用观察者模式来设计,所有的Reducer对象 订阅 用户的Actions,当用户发布 Actions 后,由Reducer自己来进一步判断。判断 则使用简单工厂方法实现即可。

所有的Reducer对象监听Dispatch对象,Reducers 拿到监听得到的Action来进一步判断下一个步骤。

功能设计理念我们已经了解,先定义一个 Global Object , 所有的状态将会从这个对象更新或者读取

let stateMapper = new Map()

以用户翻页为例,先定义 State 与 Action 类型 。我定义了 PageAction ,与 PageState 两个类型。来模拟用户翻页的时候页面状态如何改变。

interface Action {
}
interface State {
}
class PageAction implements Action {
        static readonly CURRENT: string = "[page] current";
        static readonly NEXT: string = "[page] next";;
        static readonly BEFORE: string = "[page] before";;
}
class PageState implements State {
        current: number = 0;
        next: number = 1;
        before: number = -1;
}

定义Reducer ,用来更改State类型。

const reducers = (state: PageState, action: string) => {

        switch (action) {
                case PageAction.NEXT:
                        return { ...state, current: state.current + 1, next: state.next + 1, before: state.before + 1 }
                case PageAction.BEFORE:
                        return { ...state, current: state.current - 1, next: state.next - 1, before: state.before - 1 }
                case PageAction.CURRENT:
                        return state
        }

}

这个全局对象就是我们之前定义的并且要维护的,所有状态的改变最终都会更新到这里。

stateMapper.set(pageReducers, new PageState())

定义dispatch 方法,用来触发 action

const dispatch = (action: string) => {
        let mapper = stateMapper;
        mapper.forEach((state, target) => {
                target.initState = target.reducer(target.initState,action)
        });
}

用户触发翻页操作,发现了没,无论在哪只要 dispatch 相应的 Action ,状态会自动更新

dispatch(PageAction.BEFORE)

Select:读操作,我们称为 select,读要比写简单多了

let pageSelect = (stateName: string) => {
        let state = stateMapper.get(stateName)
        return state;
}

至此都没有涉及 NGRX 相关的介绍,以上都是如果让我自己设计状态管理框架,我会如何做来介绍的。

  1. 首先我选用 JS 全局对象来管理所有状态
  2. 然后抽象了 Action 用来表示如何操作相关的状态
  3. 另外设计了当操作全局对象时候读写进行分离,抽象出了 Reducer 与 Select .
  4. 最后由 dispatch把 Action 分发给各个 Reducer,由Reducer自己来决定要不要对自己的State更新。

NGRX作为管理状态框架

定义状态

interface PageState {
    current: number;
    next: number;
    before: number;
}
interface AppState {
    pageState: PageState
}

定义 Actions

export const nextPage = createAction('[Page API] next Page'export const beforePage = createAction('[Page API] before Page');
export const currentPage = createAction('[Page API] current Page');                             

定义 Reducer

const initPageState: PageState = { next: 1, current: 0, before: -1 }
export const pageReducer = createReducer(initPageState,
    on(nextPage, (state) => {
        return ({
            ...state, next: state.next + 1,
            current: state.current + 1, before: state.before + 1
        })
    }))

定义 Selector

export const selectFeature = (state: AppState) => state.pageState;
export const selectFeaturePage = createSelector(
    selectFeature,
    (pageState: PageState) =>pageState
);

注册Reducer

StoreModule.forRoot({ pageState: pageReducer})

触发翻页

1.//构造函数引入 store
  constructor(private store: Store<AppState>) {
  }
2.//发出 next page Action
this.store.dispatch(nextPage());
3.//查询状态
 this.store.pipe(select(selectFeaturePage))

疑问:

  1. ngrx 中的 reducer 和 selector 是什么?有什么用?为什么要设计这两种方法?
  2. 什么是 State ? State以什么形式保存不同的状态?
  3. Reducer 为什么需要注册?键是为什么是状态名?状态名为什么需要与 AppState 的字段名保持一致?
  4. Selector为什么不用注册?
  5. dispatch 方法,并没有指定 state 与 reducer 为什么只用传 action 就行了?这些 reducers 如何判断哪个是自己的 action ,然后去更新自己的状态?

这些疑问也是我学习时产生的,结合我编写 ngrx 代码时的感想,总结出了上面的设计方法,实际的代码架构肯定比我设计的要复杂的多的多,但是原理是想通的。

对 ngrx 细节抒写方式,这里没有做过多的描述,因为我相信当你看完前面介绍如何自己实现状态框架时候,此刻你会豁然开朗。就笔者最近学习前端的感想来说,前端的知识更新快,且庞大,唯一不变的是原理。

当用新框架写代码的时候,只掌握背后思想,才会明白一段代码这么写的原因是什么。