Angular 與 Component store實踐示例
正文
我們知道,Angular
中因為有 service
和 rxjs
的存在,使得狀態(tài)管理在 Angular
,并非必須。
一個 BehaviorSubject 就形成了最簡單的狀態(tài)管理:
將邏輯部分分離到 service
使用 Rxjs 將 service 中的邏輯部分,拆分為狀態(tài)和方法。Component subscribe state 來更新 view, 調(diào)用方法來修改 state。
我們知道,Angular 中可以使用 NgRx 提供的 store 來做狀態(tài)管理。NgRx store 跟 Redux 本質(zhì)上是一樣的。多數(shù)時候,可能我們都覺得 Redux 太重了。而且強(qiáng)行將數(shù)據(jù)與 view 分離。
除了 ngrx store, ngrx 還提供了另外一種輕量級的狀態(tài)管理,component store。本質(zhì)上它跟我們剛剛介紹的狀態(tài)管理模式是一致的,只是提供了一些接口方便我們使用。 NgRx - @ngrx/component-store
在定位上,可以參考 hooks 與 redux 的區(qū)分,Ngrx store 用來處理全局狀態(tài),Component Store 用來 component 內(nèi)局部的狀態(tài)管理使用。(Store 和 Component 擁有相同的生命周期)。當(dāng)然,實際上使用下來,component store 也完全可以用來當(dāng)作全局的狀態(tài)管理來處理。
component store 的使用方法
我們可以看到,store 主要提供了三個方法:
- select, 用來拆分 state
- updater, 用來更新 state, 主要是無 effect 的 state 更新。
- effect, 用來處理有 effect 的情況,調(diào)用 updater 的方法來更新數(shù)據(jù)。
我們可以看出來,這樣的接口設(shè)計跟 Mobx 或者 Vuex 是比較接近的。區(qū)別就是,因為 RxJS 的緣故,它的實現(xiàn)異常簡單。幾乎只是基于 behaviorSubject 包了一層殼。
有兩點還是值得一提的:
updater 和 effect 的方法參數(shù)可以同時接受 value 和 observable value, 這使得我們在操作一個 stream 的時候,可以直接將 stream 作為參數(shù)。
比如我們現(xiàn)在有一個方法 updateUserName: (string | Observable<strring>) => void;
使用的時候,我們可以直接調(diào)用:updateUserName('zhangsan')
有時候我們在 component 里拿到的是一個 stream。
比如 form.valueChanges, 這時候我們就不需要手動 subscribe stream, 而是直接
updateUserName(form.valueChanges.pipe(map(form => form.userName)))
- updater 和 effect 將 stream 作為參數(shù)后會自動 subscribe 并在 store 被銷毀的時候 unsubscribe, 這就意味著你不用再寫一堆手動 unsubscribe 的邏輯。
在 component 中使用
在 component 中使用也比較簡單:
- 將 component 中必要的數(shù)據(jù)投喂給 store, 一般來說是 input.
- 在 component 中調(diào)用 updater 或者 effect 返回的方法修改 state。
- 在 component 中 subscribe state 實現(xiàn) view 層的渲染。
@Component({ template: `...`, // ??MoviesStore is provided higher up the component tree }) export class MovieComponent { movie$: Observable<Movie>; @Input() set movieId(value: string) { // calls effect with value. ?? Notice it's a single string value. this.moviesStore.getMovie(value); this.movie$ = this.moviesStore.selectMovie(value); } constructor(private readonly moviesStore: MoviesStore) {} }
當(dāng)然,我們也可以做一點優(yōu)化,比如,盡可能將邏輯放在 store 中 componet 只做簡單的調(diào)用。將數(shù)據(jù)之間的聯(lián)動關(guān)系放在 store 的 constructor 中,component 只做調(diào)用。
@Component({ template: `...`, // ??MoviesStore is provided higher up the component tree }) export class MovieComponent { movie$: Observable<Movie>; @Input() set movieId(value: string) { this.mobiesStore.patchState({movieId: value}); } constructor(private readonly moviesStore: MoviesStore) {} }
@Injectable() export class MoviesStore extends ComponentStore<MoviesState> { constructor(private readonly moviesService: MoviesService) { super({movieId: string; movies: []}); this.geMovie(this.movieId$); } movieId$ = this.select(state => state.movieId); movie$ = this.moviesStore.selectMovie(this.movieId$); readonly getMovie = this.effect((movieId$: Observable<string>) => { //.... }); readonly addMovie = this.updater((state, movie: Movie) => ({ // ... })); selectMovie(movieId: string) { // ... } }
因為這里的 component store 是針對單個 component 的,也就是通常情況它的使用場景是邏輯較為復(fù)雜的 component。一個 component 基于 input 的變化完全可以轉(zhuǎn)化為對于 store 的監(jiān)聽。那么,我們基本上可以將 component 的多數(shù) input 同步到 store 中。
在一段時間的使用過程中,我發(fā)現(xiàn),這是比較費勁的,
- 同一個字段,我需要在 component 和 store state 中聲明兩次。
- Input 必須轉(zhuǎn)寫成 set 模式
比如 userName 這個字段 原來: @Input userName: string; 與 store 同步: @Input set userName(val: string) { this.store.patchState({userName: val}); } 如果想在 component 中直接調(diào)用 userName 就更麻煩了。 private _userName: string; @Input set userName(val: string) { this._userName = val; this.store.patchState({userName: val}); } get userName() { return this._userName; }
如果字段比較多,簡直就是災(zāi)難。
最近在嘗試一種不同于官網(wǎng)推薦的方法。我們知道,除了 set 我們還有一種更為常規(guī)的方法獲取 input changes, 那就是 ngChanges。
export function mapPropChangesToStore<T extends JsonRecord>(this: ComponentStore<T>, mappedKeys: readonly string[], changes: SimpleChanges) { const state = mappedKeys.reduce((prev: Partial<T>, propKey) => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const propValue = changes?.[propKey]; if (!propValue) { return prev; } return ({ ...prev, [propKey]: propValue.currentValue, }); }, {}); if (isEmpty(state)) { return; } this.patchState(state); }
在 component 中對于 store 的使用
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { mapPropChangesToStore } from '@dashes/ngx-shared'; import { componentInputs, CsDemoStore } from './cs-demo.store'; @Component({ selector: 'app-cs-demo', templateUrl: './cs-demo.component.html', styleUrls: ['./cs-demo.component.css'] }) export class CsDemoComponent implements OnChanges { @Input() p1!: string; @Input() p2!: string; @Input() p3!: string; constructor(public store: CsDemoStore) { } ngOnChanges(changes: SimpleChanges): void { mapPropChangesToStore.bind(this.store)(componentInputs, changes); } } export const componentInputs = ['p1', 'p2'] as const; export type State = Pick<CsDemoComponent, typeof componentInputs[number]>;
這樣看起來就會簡潔很多。
以上就是Angular 與 Component store實踐示例的詳細(xì)內(nèi)容,更多關(guān)于Angular Component store的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在JavaScript的AngularJS庫中進(jìn)行單元測試的方法
這篇文章主要介紹了在JavaScript的AngularJS庫中進(jìn)行單元測試的方法,主要針對AngularJS中的控制器相關(guān),需要的朋友可以參考下2015-06-06解決Angular.js中使用Swiper插件不能滑動的問題
下面小編就為大家分享一篇解決Angular.js中使用Swiper插件不能滑動的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-02-02angular報錯can't resolve all parameters&nb
這篇文章主要介紹了angular報錯can't resolve all parameters for []的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03