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

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

@ngrx/store

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

关键概念

  • 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

<button (click)="increment()">Increment</button>

<div>Current count:{{count$ | async}}</div>

<button (click)="decrement()">Decrement</button>

<button (click)="reset()">Reset Counter</button>
// 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<number>;
    
    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中

<app-my-counter></app-my-counter>

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: `
    <li *ngFor="let movie of movies">
      {{ movie.name }}
    </li>
  `
})
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: `
    <div *ngFor="let movie of movies$ | async">
      {{ movie.name }}
    </div>
  `
})
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。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值