ngrx 状态管理
本文假设你对 Angualr ,Typescript 有所了解。
不同页面,组件之间如何传递数据?
- url 传递
- cookie
- session
- html5 Local storage
- lodash
- Global object
这些数据根据不同的应用场景可以抽象为不同的状态,以翻页为例,当前页是需要保存的状态,无论是向上翻页还是向下翻页都需要依赖当前的状态。
大型项目系统如何管理状态?
在大型项目系统中,可能会有十几种甚至上百种状态需要保存管理,如果使用 Local storage 或者 lodash ,毋庸置疑是最简单的方式 ,但是那就需要人为管理状态的 key ,并且手动的释放不需要的内存。
设想使用Global Object管理 State
将状态都集中保存到全局的 Js Object 中,开发一种框架对这个 Big Object 进行增删改查的状态操作。
- 读写分离
将对 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 相关的介绍,以上都是如果让我自己设计状态管理框架,我会如何做来介绍的。
- 首先我选用 JS 全局对象来管理所有状态
- 然后抽象了 Action 用来表示如何操作相关的状态
- 另外设计了当操作全局对象时候读写进行分离,抽象出了 Reducer 与 Select .
- 最后由 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))
疑问:
- ngrx 中的 reducer 和 selector 是什么?有什么用?为什么要设计这两种方法?
- 什么是 State ? State以什么形式保存不同的状态?
- Reducer 为什么需要注册?键是为什么是状态名?状态名为什么需要与 AppState 的字段名保持一致?
- Selector为什么不用注册?
- dispatch 方法,并没有指定 state 与 reducer 为什么只用传 action 就行了?这些 reducers 如何判断哪个是自己的 action ,然后去更新自己的状态?
这些疑问也是我学习时产生的,结合我编写 ngrx 代码时的感想,总结出了上面的设计方法,实际的代码架构肯定比我设计的要复杂的多的多,但是原理是想通的。
对 ngrx 细节抒写方式,这里没有做过多的描述,因为我相信当你看完前面介绍如何自己实现状态框架时候,此刻你会豁然开朗。就笔者最近学习前端的感想来说,前端的知识更新快,且庞大,唯一不变的是原理。
当用新框架写代码的时候,只掌握背后思想,才会明白一段代码这么写的原因是什么。