Immutable & Redux in Angular Way
Immutable & Redux in Angular Way写在前面AngularJS 1.x版本作为上一代MVVM的框架取得了巨大的成功,现在一提到Angular,哪怕是已经和1.x版本完全不兼容的Angular 2.x(目前最新的版本号为4.2.2),大家还是把其作为典型的MVVM框架,MVVM的优点Angular自然有,MVVM的缺点也变成了Angular的缺点一直被人诟病。 其实,从Angular 2开始,Angular的数据流动完全可以由开发者自由控制,因此无论是快速便捷的双向绑定,还是现在风头正盛的 Mutable我们以最简单的计数器应用举例,在这个例子中,counter的数值可以由按钮进行加减控制。 counter.component.ts代码 import { Component,ChangeDetectionStrategy,Input } from '@angular/core'; @Component({ selector : 'app-counter',templateUrl : './counter.component.html',styleUrls : [] }) export class CounterComponent { @Input() counter = { payload: 1 }; increment() { this.counter.payload++; } decrement() { this.counter.payload--; } reset() { this.counter.payload = 1; } } counter.component.html代码 <p>Counter: {{ counter.payload }}</p> <button (click)="increment()">Increment</button> <button (click)="decrement()">Decrement</button> <button (click)="reset()">Reset</button>
现在我们增加一下需求,要求counter的初始值可以被修改,并且将修改后的counter值传出。在Angular中,数据的流入和流出分别由@Input和@Output来控制,我们分别定义counter component的输入和输出,将counter.component.ts修改为 import { Component,Input,Output,EventEmitter } from '@angular/core'; @Component({ selector : 'app-counter',templateUrl: './counter.component.html',styleUrls : [] }) export class CounterComponent { @Input() counter = { payload: 1 }; @Output() onCounterChange = new EventEmitter<any>(); increment() { this.counter.payload++; this.onCounterChange.emit(this.counter); } decrement() { this.counter.payload--; this.onCounterChange.emit(this.counter); } reset() { this.counter.payload = 1; this.onCounterChange.emit(this.counter); } } 当其他component需要使用counter时,app.component.html代码 <counter [counter]="initCounter" (onCounterChange)="onCounterChange($event)"></counter> app.component.ts代码 import { Component } from '@angular/core'; @Component({ selector : 'app-root',templateUrl: './app.component.html',styleUrls : [ './app.component.less' ] }) export class AppComponent { initCounter = { payload: 1000 } onCounterChange(counter) { console.log(counter); } } 在这种情况下counter数据
框架本身对此并没有进行限制,如果开发者对数据的修改没有进行合理的规划时,很容易导致数据的变更难以被追踪。
与此同时,数据在任意的地方可以被修改给使用者带来了便利的同时也带来了性能的降低,由于无法预判脏值产生的时机,Angular需要在每个浏览器事件后去检查更新template中绑定数值的变化,虽然Angular做了大量的优化来保证性能,并且成果显著(目前主流前端框架的跑分对比),但是Angular也提供了另一种开发方式。 Immutable & ChangeDetection在Angular开发中,可以通过将component的
反过来说就是当@Input对象mutate时,Angular将不再进行自动脏值检测,这个时候需要保证@Input的数据为Immutable 将counter.component.ts修改为 import { Component,EventEmitter,ChangeDetectionStrategy } from '@angular/core'; @Component({ selector : 'app-counter',changeDetection: ChangeDetectionStrategy.OnPush,styleUrls : [] }) export class CounterComponent { @Input() counter = { payload: 1 }; @Output() onCounterChange = new EventEmitter<any>(); increment() { this.counter.payload++; this.onCounterChange.emit(this.counter); } decrement() { this.counter.payload--; this.onCounterChange.emit(this.counter); } reset() { this.counter.payload = 1; this.onCounterChange.emit(this.counter); } } 将app.component.ts修改为 import { Component } from '@angular/core'; @Component({ selector : 'app-root',styleUrls : [ './app.component.less' ] }) export class AppComponent { initCounter = { payload: 1000 } onCounterChange(counter) { console.log(counter); } changeData() { this.initCounter.payload = 1; } } 将app.component.html修改为 <app-counter [counter]="initCounter" (onCounterChange)="onCounterChange($event)"></app-counter> <button (click)="changeData()">change</button>
这个时候点击change发现counter的值不会发生变化。 将app.component.ts中changeData修改为 changeData() { this.initCounter = { ...this.initCounter,payload: 1 } } counter值的变化一切正常,以上的代码使用了Typescript 2.1开始支持的 Object Spread,和以下代码是等价的 changeData() { this.initCounter = Object.assign({},this.initCounter,{ payload: 1 }); }
通过保证@Input的输入Immutable可以提升Angular的性能,但是counter数据在counter component中并不是Immutable,数据的修改同样难以被追踪,下一节我们来介绍使用Redux思想来构建Angular应用。 Redux & Ngrx WayRedux来源于React社区,时至今日已经基本成为React的标配了。Angular社区实现Redux思想最流行的第三方库是ngrx,借用官方的话来说
基本概念和Redux一样,ngrx也有着相同View、Action、
我们使用ngrx构建同样的counter应用,与之前不同的是这次需要依赖 Componentapp.module.ts代码,将counterReducer通过StoreModule import import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {HttpModule} from '@angular/http'; import {AppComponent} from './app.component'; import {StoreModule} from '@ngrx/store'; import {counterReducer} from './stores/counter/counter.reducer'; @NgModule({ declarations: [ AppComponent ],imports: [ BrowserModule,FormsModule,HttpModule,StoreModule.provideStore(counterReducer),],providers: [],bootstrap: [AppComponent] }) export class AppModule { } 在NgModule中使用ngrx提供的StoreModule将我们的counterReducer传入 app.component.html <p>Counter: {{ counter | async }}</p> <button (click)="increment()">Increment</button> <button (click)="decrement()">Decrement</button> <button (click)="reset()">Reset</button> 注意多出来的async的pipe,async管道将自动subscribe Observable或Promise的最新数据,当Component销毁时,async管道会自动unsubscribe。 app.component.ts import {Component} from '@angular/core'; import {CounterState} from './stores/counter/counter.store'; import {Observable} from 'rxjs/observable'; import {Store} from '@ngrx/store'; import {DECREMENT,INCREMENT,RESET} from './stores/counter/counter.action'; @Component({ selector: 'app-root',styleUrls: ['./app.component.css'] }) export class AppComponent { counter: Observable<number>; constructor(private store: Store<CounterState>) { this.counter = store.select('counter'); } increment() { this.store.dispatch({ type: INCREMENT,payload: { value: 1 } }); } decrement() { this.store.dispatch({ type: DECREMENT,payload: { value: 1 } }); } reset() { this.store.dispatch({type: RESET}); } } 在Component中可以通过依赖注入ngrx的Store,通过Store select获取到的counter是一个Observable的对象,自然可以通过async pipe显示在template中。 dispatch方法传入的内容包括 Actioncounter.action.ts export const INCREMENT = 'INCREMENT'; export const DECREMENT = 'DECREMENT'; export const RESET = 'RESET'; Action部分很简单,reducer要根据dispath传入的action执行不同的操作。 Reducercounter.reducer.ts import {CounterState,INITIAL_COUNTER_STATE} from './counter.store'; import {DECREMENT,RESET} from './counter.action'; import {Action} from '@ngrx/store'; export function counterReducer(state: CounterState = INITIAL_COUNTER_STATE,action: Action): CounterState { const {type,payload} = action; switch (type) { case INCREMENT: return {...state,counter: state.counter + payload.value}; case DECREMENT: return {...state,counter: state.counter - payload.value}; case RESET: return INITIAL_COUNTER_STATE; default: return state; } } Reducer函数接收两个参数,分别是state和action,根据Redux的思想,reducer必须为纯函数(Pure Function),注意这里再次用到了上文提到的Object Spread。 Storecounter.store.ts export interface CounterState { counter: number; } export const INITIAL_COUNTER_STATE: CounterState = { counter: 0 }; Store部分其实也很简单,定义了couter的Interface和初始化state。 以上就完成了Component->Action->Reducer->Store->Component的单向数据流动,当counter发生变更的时候,component会根据counter数值的变化自动变更。 总结同样一个计数器应用,Angular其实提供了不同的开发模式
这篇文章总结了很多Ngrx优缺点,其中我觉得比较Ngrx显著的优点是
Ngrx的缺点也很明显
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |