Angular狀態管理框架NgRx入門

ngrx概念

  • 一個框架
  • 用於構建Angular8響應式應用
  • 用於狀態管理
  • 使用可觀察對象
  • 使用Typescript
  • 使用OnPush策略,變更檢測更高效
  • 狀態序列化存儲
  • 易於測試

原理圖

ngrx

  • component產生action(事件)
  • action觸發effect,業務處理數據
  • store中存儲state、reducer
  • reducer產生新的state
  • state修改,更新component

簡單入門

初始化項目

ng new ngrx-demo
> ng new ngrx-demo 
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS   [ https://sass-lang.com/
documentation/syntax#scss                ]
CREATE ngrx-demo/README.md (1026 bytes)
CREATE ngrx-demo/.editorconfig (246 bytes)
CREATE ngrx-demo/.gitignore (631 bytes)
CREATE ngrx-demo/angular.json (3705 bytes)
CREATE ngrx-demo/package.json (1295 bytes)
CREATE ngrx-demo/tsconfig.json (543 bytes)
CREATE ngrx-demo/tslint.json (1953 bytes)
CREATE ngrx-demo/browserslist (429 bytes)
CREATE ngrx-demo/karma.conf.js (1021 bytes)
CREATE ngrx-demo/tsconfig.app.json (270 bytes)
CREATE ngrx-demo/tsconfig.spec.json (270 bytes)
CREATE ngrx-demo/src/favicon.ico (948 bytes)
CREATE ngrx-demo/src/index.html (294 bytes)
CREATE ngrx-demo/src/main.ts (372 bytes)
....
added 1461 packages from 1071 contributors in 85.831s
    Successfully initialized git.

安裝ngrx

npm install @ngrx/store --save

創建Angular組件my-counter

ng generate @schematics/angular:component my-counter --style=scss <

定義action

//app/counter.actions.ts
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');

定義reducer初始狀態爲數值

//app/counter.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { increment, decrement, reset } from './counter.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);
}

註冊state

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 })     // 註冊時指定key
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

定義組件

# app / my-counter / my-counter.component.html
<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>

組件觸發action

# 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')); //這裏關鍵點,在於app.module.ts註冊時候的key
  }

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

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

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

組件生效

// app.component.html 
<div>
  this is app component 
  <app-my-counter></app-my-counter>
</div>
//界面按鈕點擊,觀測數據變化

store.pipe寫法解惑

//查看store源碼,我們得到Observable 
export declare class Store<T> extends Observable<T> implements Observer<Action> {
  ....
}

store.select也是Observable

export declare function select<T, Props, K>(mapFn: (state: T, props: Props) => K, props?: Props): (source$: Observable<T>) => Observable<K>;
this.store
  .select(fromStore.getProductsState)
  .map(state => state.pizzas)
  .map(pizzas => pizza.entities);

進階

當reducer初始狀態state爲對象時

//counter.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { increment, decrement, reset } from './counter.actions';

export interface State {
  away: number;
}

export const initialState: State = {  
  away: 0,
};

const _counterReducer = createReducer(initialState,
  on(increment, state =>  ({...state, away: state.away   1})),
  on(decrement, state => ({...state, away: state.away - 1})),
  on(reset, state => ({...state, away: 0})),
);

export function counterReducer(state, action) {
  return _counterReducer(state, action);
}

延遲加載state註冊

# 新增文件app/feature.module.ts
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter.reducer';

@NgModule({
  imports: [
    StoreModule.forFeature('countFeture', counterReducer) # 需要注意,對應的調整
  ],
})
export class ScoreboardModule {}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter.reducer';
import { MyCounterComponent } from './my-counter/my-counter.component';
import { ScoreboardModule } from './feature.module';

@NgModule({
  declarations: [
    AppComponent,
    MyCounterComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    StoreModule.forRoot({}),
    ScoreboardModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

參考文獻:

本文作者:前端首席體驗師(CheongHu)

聯繫郵箱:[email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章