美文网首页
Angular状态管理 - NgRx

Angular状态管理 - NgRx

作者: 风之化身呀 | 来源:发表于2020-05-13 21:47 被阅读0次

    Angular 官方框架本身提供了较为全面的功能,但是却缺少诸如 Redux,Vuex 之类的状态管理框架,而 NgRx 就是弥补这个缺陷的一个第三方框架。参考了 Redux 的思想,结合了 RxJs 流的处理

    1、@ngrx/store

    • 基本使用
    // 1、创建 Action
    import { createAction } from '@ngrx/store';
    
    export const increment = createAction('[Counter Component] Increment');
    export const decrement = createAction('[Counter Component] Decrement');
    export const reset = createAction('[Counter Component] Reset');
    
    // 2、创建 Reducer
    import { createReducer, on } from '@ngrx/store';
    import { increment, decrement, reset } from './actions';
    
    export const initialState = 0;
    const _counterReducer = createReducer(initialState,
      on(increment, state => state + 1),
      on(decrement, state => state - 1),
      on(reset, state => 0),
    );
    export function counterReducer(state, action) {
      return _counterReducer(state, action);
    }
    // 3、AppModule 中引入
    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { AppComponent } from './app.component';
    
    import { StoreModule } from '@ngrx/store';
    import { counterReducer } from './reducer';
    
    @NgModule({
      declarations: [AppComponent],
      imports: [
        BrowserModule,
        StoreModule.forRoot({ count: counterReducer })
      ],
      providers: [],
      bootstrap: [AppComponent],
    })
    export class AppModule {}
    // 4、组件中调用
    import { Component } from '@angular/core';
    import { Store, select } from '@ngrx/store';
    import { Observable } from 'rxjs';
    import { increment, decrement, reset } from '../actions';
    
    @Component({
      selector: 'app-my-counter',
      templateUrl: './my-counter.component.html',
      styleUrls: ['./my-counter.component.css'],
    })
    export class MyCounterComponent {
      count$: Observable<number>;
    
      constructor(private store: Store<{ count: number }>) {  //依赖注入
        this.count$ = store.pipe(select('count'));  // 使用 select 函数取值
      }
    
      increment() {
        this.store.dispatch(increment());
      }
    
      decrement() {
        this.store.dispatch(decrement());
      }
    
      reset() {
        this.store.dispatch(reset());
      }
    }
    
    <button id="increment" (click)="increment()">Increment</button>
    
    <div>Current Count: {{ count$ | async }}</div>  // 管道符取值
    
    <button id="decrement" (click)="decrement()">Decrement</button>
    
    <button id="reset" (click)="reset()">Reset Counter</button>
    
    • 进阶

    1、createAction 第二个函数参数

    import { createAction, props } from '@ngrx/store';
    
    export const login = createAction(
      '[Login Page] Login',
      props<{ username: string; password: string }>()
    );
    
    onSubmit(username: string, password: string) {
      store.dispatch(login({ username: username, password: password }));
    }
    

    2、feature State

    // 定义一个子状态
    import { createSelector, createFeatureSelector } from '@ngrx/store';
    
    export const featureKey = 'feature';
    
    export interface FeatureState {
      counter: number;
    }
    
    export interface AppState {
      feature: FeatureState;
    }
    
    export const selectFeature = createFeatureSelector<AppState, FeatureState>(featureKey);
    
    export const selectFeatureCount = createSelector(
      selectFeature,
      (state: FeatureState) => state.counter
    );
    // 2、子模块上注册子状态
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import * as fromScoreboard from './reducers/scoreboard.reducer';
    
    @NgModule({
      imports: [
        StoreModule.forFeature(fromScoreboard.featureKey, fromScoreboard.reducer)
      ],
    })
    export class ScoreboardModule {}
    
    // 2、在根模块注册
    import { NgModule } from '@angular/core';
    import { StoreModule } from '@ngrx/store';
    import { ScoreboardModule } from './scoreboard/scoreboard.module';
    
    @NgModule({
      imports: [
        StoreModule.forRoot({}),
        ScoreboardModule
      ],
    })
    export class AppModule {}
    

    3、createSelector 参数函数的第二个参数

    export const getCount = createSelector(
      getCounterValue,
      (counter, props) => counter * props.multiply
    );
    
    ngOnInit() {
      this.counter$ = this.store.pipe(select(fromRoot.getCount, { multiply: 2 }))
    }
    

    4、meta-reducer
    本质上相当于 Redux 的中间价

    import { StoreModule, ActionReducer, MetaReducer } from '@ngrx/store';
    import { reducers } from './reducers';
    
    // console.log all actions
    export function debug(reducer: ActionReducer<any>): ActionReducer<any> {
      return function(state, action) {
        console.log('state', state);
        console.log('action', action);
    
        return reducer(state, action);
      };
    }
    
    export const metaReducers: MetaReducer<any>[] = [debug];
    
    @NgModule({
      imports: [
        StoreModule.forRoot(reducers, { metaReducers })
      ],
    })
    export class AppModule {}
    

    2、@ngrx/effects

    effects 用于监听 action,是一种异步 action 处理方案,类似 redux-saga

    1、基础

    // 1、创建 effect
    import { Injectable } from '@angular/core';
    import { Actions, createEffect, ofType } from '@ngrx/effects';
    import { of } from 'rxjs';
    import { map, mergeMap, catchError } from 'rxjs/operators';
    import { MoviesService } from './movies.service';
    
    @Injectable()
    export class MovieEffects {
      loadMovies$ = createEffect(() =>
        this.actions$.pipe(
          ofType('[Movies Page] Load Movies'),
          mergeMap(() => this.moviesService.getAll()
            .pipe(
              map(movies => ({ type: '[Movies API] Movies Loaded Success', payload: movies })),
              catchError(() => of({ type: '[Movies API] Movies Loaded Error' }))
            )
          )
        )
      );
    
      constructor(
        private actions$: Actions,
        private moviesService: MoviesService
      ) {}
    }
    // 2、注册 effect
    import { EffectsModule } from '@ngrx/effects';
    import { MovieEffects } from './effects/movie.effects';
    
    @NgModule({
      imports: [
        EffectsModule.forRoot([MovieEffects])
      ],
    })
    export class AppModule {}
    
    // 或者注册 feature effect
    import { EffectsModule } from '@ngrx/effects';
    import { MovieEffects } from './effects/movie.effects';
    
    @NgModule({
      imports: [
        EffectsModule.forFeature([MovieEffects])
      ],
    })
    export class MovieModule {}
    

    2、进阶

    a、处理不 dispatch action 的 effect

    import { Injectable } from '@angular/core';
    import { Actions, createEffect } from '@ngrx/effects';
    import { tap } from 'rxjs/operators';
    
    @Injectable()
    export class LogEffects {
      constructor(private actions$: Actions) {}
      
      logActions$ = createEffect(() =>
        this.actions$.pipe(
          tap(action => console.log(action))
        ), { dispatch: false });  // 这里 dispatch:false 表示该action不会 dispatch
    }
    

    b、effect 错误处理: 关闭 resubscriptions

    import { Injectable } from '@angular/core';
    import { Actions, ofType, createEffect } from '@ngrx/effects';
    import { of } from 'rxjs';
    import { catchError, exhaustMap, map } from 'rxjs/operators';
    import {
      LoginPageActions,
      AuthApiActions,
    } from '../actions';
    import { AuthService } from '../services/auth.service';
    
    @Injectable()
    export class AuthEffects {
      logins$ = createEffect(
        () =>
          this.actions$.pipe(
            ofType(LoginPageActions.login),
            exhaustMap(action =>
              this.authService.login(action.credentials).pipe(
                map(user => AuthApiActions.loginSuccess({ user })),
                catchError(error => of(AuthApiActions.loginFailure({ error })))
              )
            )
            // Errors are handled and it is safe to disable resubscription
          ),
        { useEffectsErrorHandler: false }
      );
    
      constructor(
        private actions$: Actions,
        private authService: AuthService
      ) {}
    }
    

    c、自定义 effect 错误处理
    EFFECTS_ERROR_HANDLER

    3、其它

    • @ngrx/store-devtools
      调试工具
    • @ngrx/router-store
      跟 Angular 路由结合
    • @ngrx/entity
      方便 reducer 操作 state
    • @ngrx/data
      将 action、reducer、entity、effect 等进行了高度组合抽象,提供了一系列更新 entity 的 API。就是说数据的更新得用它的api来,不是很自由
    import { Component, OnInit } from '@angular/core';
    import { Observable } from 'rxjs';
    import { Hero } from '../../core';
    import { HeroService } from '../hero.service';
    
    @Component({
      selector: 'app-heroes',
      templateUrl: './heroes.component.html',
      styleUrls: ['./heroes.component.scss']
    })
    export class HeroesComponent implements OnInit {
      loading$: Observable<boolean>;
      heroes$: Observable<Hero[]>;
    
      constructor(private heroService: HeroService) {
        this.heroes$ = heroService.entities$;
        this.loading$ = heroService.loading$;
      }
    
      ngOnInit() {
        this.getHeroes();
      }
    
      add(hero: Hero) {
        this.heroService.add(hero);
      }
    
      delete(hero: Hero) {
        this.heroService.delete(hero.id);
      }
    
      getHeroes() {
        this.heroService.getAll();
      }
    
      update(hero: Hero) {
        this.heroService.update(hero);
      }
    }
    

    参考

    https://ngrx.io/docs

    相关文章

      网友评论

          本文标题:Angular状态管理 - NgRx

          本文链接:https://www.haomeiwen.com/subject/kofnnhtx.html