Angular状态管理 - NgRx

2020-05-13  本文已影响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、其它

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

上一篇下一篇

猜你喜欢

热点阅读