写点什么

浅析 Angular 数据状态管理框架:NgRx/Store

用户头像
devpoint
关注
发布于: 4 小时前
浅析Angular数据状态管理框架:NgRx/Store

ngrx/store 是基于 RxJS 的状态管理库,其灵感来源于Redux。在NgRx中,状态是由一个包含actionreducer的函数的映射组成的。Reducer函数经由action的分发以及当前或初始的状态而被调用,最后由reducer返回一个不可变的状态。



状态管理

在前端大型复杂Angular/AngularJS项目的状态管理一直是个让人头疼的问题。在 AngularJS(1.x 版本)中,状态管理通常由服务,事件,$rootScope混合处理。在 Angular 中(2+版本),组件通信让状态管理变得清晰一些,但还是有点复杂,根据数据流向不同会用到很多方法。

ngrx/store 中的基本原则

视图层通过dispatch发起一个行为(action)、Reducer接收action,根据action.type类型来判断执行、改变状态、返回一个新的状态给store、由store更新state



  • State(状态) 是状态(state)存储器

  • Action(行为) 描述状态的变化

  • Reducer(归约器/归约函数) 根据先前状态以及当前行为来计算出新的状态,里面的方法为纯函数

  • 状态用State的可观察对象,Action的观察者——Store来访问

Actions(行为)

Actions是信息的载体,它发送数据到reducer,然后reducer更新storeActionsstore能接受数据的唯一方式。


ngrx/store里,Action的接口是这样的:


// actions包括行为类型和对应的数据载体export interface Action {  type: string;  payload?: any;}
复制代码


type描述期待的状态变化类型。比如,添加待办 ADD_TODO,增加 DECREMENT 等。payload是发送到待更新store中的数据。store派发action 的代码类似如下:


// 派发action,从而更新storestore.dispatch({  type: 'ADD_TODO',  payload: 'Buy milk'});
复制代码

Reducers(归约器)

Reducers规定了行为对应的具体状态变化。是纯函数,通过接收前一个状态和派发行为返回新对象作为下一个状态的方式来改变状态,新对象通常用Object.assign和扩展语法来实现。


// reducer定义了action被派发时state的具体改变方式export const todoReducer = (state = [], action) => {  switch(action.type) {    case 'ADD_TODO':      return [...state, action.payload];    default:      return state;  }}
复制代码


开发时特别要注意函数的纯性。因为纯函数:


  • 不会改变它作用域外的状态

  • 输出只决定于输入

  • 相同输入,总是得到相同输出

Store(存储)

store中储存了应用中所有的不可变状态。ngrx/store 中的 store 是 RxJS 状态的可观察对象,以及行为的观察者。


可以利用 Store 来派发行为。也可以用 Store 的 select()方法获取可观察对象,然后订阅观察,在状态变化之后做出反应。


上面我们描述的是基本流程。在实际开发过程中,会涉及 API 请求、浏览器存储等异步操作,就需要 effects 和 services,effects 由 action 触发,进行一些列逻辑后发出一个或者多个需要添加到队列的 action,再由 reducers 处理。



使用 ngrx/store 框架开发应用,始终只维护一个状态,并减少对 API 的调用。

实战

实战主要介绍一个管理系统的登录模块。

创建 Form 表单

  1. 增加组件:LoginComponent,主要就是布局,代码为组件逻辑

  2. 定义用户:User Model


export class User {    id: number;    username: string;    password: string;    email: string;    avatar: string;
clear(): void { this.id = undefined; this.username = ""; this.password = ""; this.email = ""; this.avatar = "./assets/default.jpg"; }}
复制代码


  1. 添加表单:在组件LoginComponent增加Form表单

NGRX Store

按照上述的 4 个原则定义相应的Actions



  • reducers定义状态

  • 在文件auth.reducers.ts中创建状态,并初始化


export interface AuthState {    isAuthenticated: boolean;    user: User | null;    errorMessage: string | null;}
export const initialAuthState: AuthState = { isAuthenticated: false, user: null, errorMessage: null};
复制代码


  • actions定义行为


export enum AuthActionTypes {    Login = "[Auth] Login",    LoginSuccess = "[Auth] Login Success",    LoginFailure = "[Auth] Login Failure"}
export class Login implements Action { readonly type = AuthActionTypes.Login; constructor(public payload: any) {}}
复制代码


  • service实现数据交互(服务器)


@Injectable()export class AuthService {    private BASE_URL = "api/user";
constructor(private http: HttpClient) {}
getToken(): string { return localStorage.getItem("token"); }
login(email: string, pwd: string): Observable<any> { const url = `${this.BASE_URL}/login`; return this.http.post<User>(url, { email, pwd }); }}
复制代码


  • effects 侦听从 Store 调度的动作,执行某些逻辑,然后分派新动作

  • 一般情况下只在这里调用 API

  • 通过返回一个 action 给 reducer 进行操作来改变 store 的状态

  • effects 总是返回一个或多个 action(除非@Effect with {dispatch: false})



@Effect()Login: Observable<any> = this.actions.pipe(    ofType(AuthActionTypes.Login),   //执行Login响应    map((action: Login) => action.payload),    switchMap(payload => {        return this.authService.login(payload.email, payload.password).pipe(            map(user => {                return new LoginSuccess({ uid: user.id, email: payload.email });            }),            catchError(error => {                return of(new LoginFailure(error));            })        );    }));
//失败的效果@Effect({ dispatch: false })LoginFailure: Observable<any> = this.actions.pipe(ofType(AuthActionTypes.LoginFailure));
//成功的效果@Effect({ dispatch: false })LoginSuccess: Observable<any> = this.actions.pipe( ofType(AuthActionTypes.LoginSuccess), tap(user => { localStorage.setItem("uid", user.payload.id); this.router.navigateByUrl("/sample"); }));
复制代码


发布于: 4 小时前阅读数: 4
用户头像

devpoint

关注

细节的追求者 2011.11.12 加入

专注前端开发,用技术创造价值!

评论

发布
暂无评论
浅析Angular数据状态管理框架:NgRx/Store