@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
StoreModuleto.- project - name of the project defined in your
angular.jsonto help locating the module to add theStoreModuleto.- module - name of file containing the module that you wish to add the import for the
StoreModuleto. 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.
此命令会自动添加以下:
- Update
package.json>dependencieswith@ngrx/store.- Run
npm installto install those dependencies.- Create a
src/app/reducersfolder, unless thestatePathflag is provided, in which case this would be created based on the flag.- Create a
src/app/reducers/index.tsfile with an emptyStateinterface, an emptyreducersmap, and an emptymetaReducersarray. This may be created under a different directory if thestatePathflag is provided.- Update your
src/app/app.module.ts>importsarray withStoreModule.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环境的安装了。
- 生成一个新的项目 StackBlitz
- 在app目录下创建一个新的文件 counter.actions.ts 。在此文件中定义三个actions :increment , decrement,reset 。
//src/app/counter.actions.tsimport {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.htmlCurrent count:{{count$ | async}}
// src/app/my-counter/my-counter.component.tsimport { 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.tsimport { 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.tsimport { 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.tsimport { 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。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
