@ngrx/store 7 & @ngrx/effects 7 + angular的使用

官方网址:https://v7.ngrx.io/guide/store

@ngrx/store

@ngrx/store是基于RxJS的状态管理库,其灵感来源于Redux。

@ngrx/store 7 & @ngrx/effects 7 + angular的使用_第1张图片

关键概念

  • Actions

Actions是NgRx中的主要模块之一。Actions表示从组件和服务中分派的唯一事件。从用户与页面的交互、通过网络请求的外部交互、与API的交互等等都可用action来描述。(可理解为客户端的数据库)

  •  Reducers

reducers 通过根据actions type来处理app中从一种状态到下一种状态的转换。reducers是纯函数,没有副作用,其返回值类型是由其输入值的类型决定的。可以同步处理每个状态的转换。每一个reducer都会处理最新的action dispatch,即当前的状态,并决定返回一个新修改的状态还是原始状态。(可理解为数据库中的数据表)

  • Selectors

选择器是用于选择、派生和组合状态块的纯函数。

使用 createSelector和createFeatureSelector函数时,@ngrx/store会track调用 selector function的最新参数。因为selectors是纯函数,所以当参数匹配时可以返回最后一个结果,而无需重新调用selector function。这就带来了性能优势。这种做法被称为记忆法。

 Installation

npm install @ngrx/store --save

 如果你用的是Angular cli 6+ ,你可以使用以下命令 

ng add @ngrx/store

Optional ng add flags

  • path - path to the module that you wish to add the import for the StoreModule to.
  • project - name of the project defined in your angular.json to help locating the module to add the StoreModule to.
  • module - name of file containing the module that you wish to add the import for the StoreModule to. Can also include the relative path to the file. For example, src/app/app.module.ts;
  • statePath - The file path to create the state in. By default, this is reducers.
  • stateInterface - The type literal of the defined interface for the state. By default, this is State.

此命令会自动添加以下:

  1. Update package.json > dependencies with @ngrx/store.
  2. Run npm install to install those dependencies.
  3. Create a src/app/reducers folder, unless the statePath flag is provided, in which case this would be created based on the flag.
  4. Create a src/app/reducers/index.ts file with an empty State interface, an empty reducers map, and an empty metaReducers array. This may be created under a different directory if the statePath flag is provided.
  5. Update your src/app/app.module.ts > imports array with StoreModule.forRoot(reducers, { metaReducers }). If you provided flags then the command will attempt to locate and update module found by the flags.

 Getting Started

下面的例子是管理计数器的状态以及计数器在页面的显示。

开始之前,先确保你安装了angular的运行环境。在此就不赘述angular环境的安装了。

  1. 生成一个新的项目 StackBlitz
  2. 在app目录下创建一个新的文件 counter.actions.ts 。在此文件中定义三个actions :increment , decrement,reset 。
//src/app/counter.actions.ts

import {Action} from '@ngrx/store';

export enum ActionTypes{
    Increment = '[Counter Component] Increment',
    Decrement = '[Counter Component] Decrement',
    Reset = '[Counter Component] Reset',
}

export class Increment implements Action{
    readonly type = ActionTypes.Increment; 
}

export class Decrement implements Action {
  readonly type = ActionTypes.Decrement;
}
 
export class Reset implements Action {
  readonly type = ActionTypes.Reset;
}

   3. 创建一个reducer ,根据actions来处理计数器值的变化

// src/app/counter.reducer.ts
import { Action } from '@ngrx/store';
import { ActionTypes } from './counter.actions';

export const initialState = 0;

export function counterReducer(state = initialState,action:Action){
    switch(action.type){
        case ActionTypes.Increment:
            return state + 1 ;

        case ActionTypes.Decrement:
            return state - 1;


        case ActionTypes.Reset:
            return 0;


        default:
            return state;
    }
}

   4. import StoreModule to app.module.ts

import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter.reducer';

 5. 在AppModule中的imports中添加 StoreModule.forRoot 

// src/app/app.module.ts (StoreModule)

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
 
import { AppComponent } from './app.component';
 
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter.reducer';
 
@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    StoreModule.forRoot({ count: counterReducer })
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

 6. 创建一个my-counter的组件

// src/app/my-counter/my-counter.component.html



Current count:{{count$ | async}}
// src/app/my-counter/my-counter.component.ts

import { Component } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { Observable } from 'rxjs';
import { Increment, Decrement, Reset } from '../counter.actions';
 
@Component({
  selector: 'app-my-counter',
  templateUrl: './my-counter.component.html',
  styleUrls: ['./my-counter.component.css'],
})
export class MyCounterComponent {
    count$:Observable;
    
    constructor(private store:Store<{count:number}>){

        this.count$ = store.pipe(select('count'))
    }

    increment(){
        this.store.dispatch(new Increment());
    }

    decrement() {
        this.store.dispatch(new Decrement());
    }

    reset() {
        this.store.dispatch(new Reset());
    }   
}

7. 添加MyCounterComponent 到AppComponent template中

8.ok了,点击increment,decrement,reset就可以更改counter的状态了。

 

@ngrx/effects

@ngrx/effects 提供一套API(装饰器@effect和action)来帮助检查store.dispatch()发出来的action。将特定类型的action过滤出来进行处理,监听特定的action,当特定的action发出之后,自动执行某些操作,然后将处理的结果重新发送回给store中。

核心概念:

  • 监听派发出来的(store.dispatch)的action
  • 隔离业务和组件(component只通过select state 和dispatch action)即可
  • 提供新的reducer state(基于网络请求、web socket消息或timer事件驱动等)

Installation

npm install @ngrx/effects --save

Getting Started

以下例子通过比较service-based 和有effect的做一下对比

1. 传统的service-based的component:

//movies-page.component.ts

@Component({
  template: `
    
  • {{ movie.name }}
  • ` }) export class MoviesPageComponent { movies: Movie[]; constructor(private movieService: MoviesService) {} ngOnInit() { this.movieService.getAll().subscribe(movies => this.movies = movies); } }
    // movies.service.ts
    
    @Injectable({
      providedIn: 'root'
    })
    export class MoviesService {
      constructor (private http: HttpClient) {}
    
      getAll() {
        return this.http.get('/movies');
      }
    }

    2.  effects处理外部数据和交互。使用effects重构一下代码:

    // movies-page.component.ts
    
    @Component({
      template: `
        
    {{ movie.name }}
    ` }) export class MoviesPageComponent { movies$: Observable = this.store.select(state => state.movies); constructor(private store: Store<{ movies: Movie[] >}) {} ngOnInit() { this.store.dispatch({ type: '[Movies Page] Load Movies' }); } }

    解析:the movies 任然是通过moviesService获取,但是component不在care movie是如何获取和加载的。component只负责声明加载movie的意图和使用select来访问movie。获取movie的异步操作是在effects中实现的。

    3. 写effects

    // movie.effects.ts
    
    import { Injectable } from '@angular/core';
    import { Actions, Effect, ofType } from '@ngrx/effects';
    import { EMPTY } from 'rxjs';
    import { map, mergeMap } from 'rxjs/operators';
    
    @Injectable()
    export class MovieEffects {
    
      @Effect()
      loadMovies$ = this.actions$
        .pipe(
          ofType('[Movies Page] Load Movies'),
          mergeMap(() => this.moviesService.getAll()
            .pipe(
              map(movies => ({ type: '[Movies API] Movies Loaded Success', payload: movies })),
              catchError(() => EMPTY)
            ))
          )
        );
    
      constructor(
        private actions$: Actions,
        private moviesService: MoviesService
      ) {}
    }

     解析:loadMovies$ effects 通过actions stream 监听所有的派发的actions,但是使用ofType之后,只对 [Movies Page] Load Movies 感兴趣。然后使用mergeMap()来将action stream映射到一个新的observable对象。this.moviesService.getAll()会返回一个observable,这个observable会将movies映射到一个新的action [Movies API] Movies Loaded Success,若错误发生,返回一个空对象。如果需要进行状态更改,这个action会被派发到store,由reducer处理。

    4. 注册root effect  和 feature effect

    // app.module.ts
    
    import { EffectsModule } from '@ngrx/effects';
    import { MovieEffects } from './effects/movie.effects';
    
    @NgModule({
      imports: [
        EffectsModule.forRoot([MovieEffects])
      ],
    })
    export class AppModule {}

    注:EffectsModule.forRoot( ) 必须在AppModule下注册,如果不需要注册任何根级别的Effect,可以Provider一个空数组。

    //admin.module.ts
    
    import { EffectsModule } from '@ngrx/effects';
    import { MovieEffects } from './effects/movie.effects';
    
    @NgModule({
      imports: [
        EffectsModule.forFeature([MovieEffects])
      ],
    })
    export class MovieModule {}

    注:通过forRoot 或forFeature 多次运行一个effect class,并不会导致effect会运行多次。forRoot和forFeature的加载效果在功能上是没有区别的,二者的区别在于forRoot设置了effects 所需的providers。

     

    你可能感兴趣的:(前端)