摘要:來源于社區,時至今日已經基本成為的標配了。部分很簡單,要根據傳入的執行不同的操作。當性能遇到瓶頸時基本不會遇到,可以更改,保證傳入數據來提升性能。當不再能滿足程序開發的要求時,可以嘗試使用進行函數式編程。
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的數據流動完全可以由開發者自由控制,因此無論是快速便捷的雙向綁定,還是現在風頭正盛的Redux,在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代碼
Counter: {{ counter.payload }}
現在我們增加一下需求,要求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(); 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代碼
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數據
會被當前counter component中的函數修改
也可能被initCounter修改
如果涉及到服務端數據,counter也可以被Service修改
在復雜的應用中,還可能在父component通過@ViewChild等方式獲取后被修改
框架本身對此并沒有進行限制,如果開發者對數據的修改沒有進行合理的規劃時,很容易導致數據的變更難以被追蹤。
與AngularJs 1.x版本中在特定函數執行時進行臟值檢查不同,Angular 2+使用了zone.js對所有的常用操作進行了monkey patch,有了zone.js的存在,Angular不再像之前一樣需要使用特定的封裝函數才能對數據的修改進行感知,例如ng-click或者$timeout等,只需要正常使用(click)或者setTimeout就可以了。
與此同時,數據在任意的地方可以被修改給使用者帶來了便利的同時也帶來了性能的降低,由于無法預判臟值產生的時機,Angular需要在每個瀏覽器事件后去檢查更新template中綁定數值的變化,雖然Angular做了大量的優化來保證性能,并且成果顯著(目前主流前端框架的跑分對比),但是Angular也提供了另一種開發方式。
Immutable & ChangeDetection在Angular開發中,可以通過將component的changeDetection定義為ChangeDetectionStrategy.OnPush從而改變Angular的臟值檢查策略,在使用OnPush模式時,Angular從時刻進行臟值檢查的狀態改變為僅在兩種情況下進行臟值檢查,分別是
當前component的@Input輸入值發生更換
當前component或子component產生事件
反過來說就是當@Input對象mutate時,Angular將不再進行自動臟值檢測,這個時候需要保證@Input的數據為Immutable
將counter.component.ts修改為
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from "@angular/core"; @Component({ selector : "app-counter", changeDetection: ChangeDetectionStrategy.OnPush, templateUrl : "./counter.component.html", styleUrls : [] }) export class CounterComponent { @Input() counter = { payload: 1 }; @Output() onCounterChange = new EventEmitter(); 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", templateUrl: "./app.component.html", styleUrls : [ "./app.component.less" ] }) export class AppComponent { initCounter = { payload: 1000 } onCounterChange(counter) { console.log(counter); } changeData() { this.initCounter.payload = 1; } }
將app.component.html修改為
這個時候點擊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 }); }
在ChangeDetectionStrategy.OnPush時,可以通過ChangeDetectorRef.markForCheck()進行臟值檢查,官網范點擊此處,手動markForCheck可以減少Angular進行臟值檢查的次數,但是不僅繁瑣,而且也不能解決數據變更難以被追蹤的問題。
通過保證@Input的輸入Immutable可以提升Angular的性能,但是counter數據在counter component中并不是Immutable,數據的修改同樣難以被追蹤,下一節我們來介紹使用Redux思想來構建Angular應用。
Redux & Ngrx WayRedux來源于React社區,時至今日已經基本成為React的標配了。Angular社區實現Redux思想最流行的第三方庫是ngrx,借用官方的話來說RxJS powered,inspired by Redux,靠譜。
基本概念如果你對RxJS有進一步了解的興趣,請訪問https://rxjs-cn.github.io/rxj...
和Redux一樣,ngrx也有著相同View、Action、Middleware、Dispatcher、Store、Reducer、State的概念。使用ngrx構建Angular應用需要舍棄Angular官方提供的@Input和@Output的數據雙向流動的概念。改用Component->Action->Reducer->Store->Component的單向數據流動。
以下部分代碼來源于CounterNgrx和這篇文章
我們使用ngrx構建同樣的counter應用,與之前不同的是這次需要依賴@ngrx/core和@ngrx/store
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
Counter: {{ counter | async }}
注意多出來的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", templateUrl: "./app.component.html", styleUrls: ["./app.component.css"] }) export class AppComponent { counter: Observable; constructor(private store: Store ) { 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方法傳入的內容包括type和payload兩部分, reducer會根據type 和payload生成不同的state,注意這里的store其實也是個Observable對象,如果你熟悉Subject,你可以暫時按照Subject的概念來理解它,store也有一個next方法,和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, INCREMENT, 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其實提供了不同的開發模式
Angular默認的數據流和臟值檢查方式其實適用于絕大部分的開發場景。
當性能遇到瓶頸時(基本不會遇到),可以更改ChangeDetection,保證傳入數據Immutable來提升性能。
當MVVM不再能滿足程序開發的要求時,可以嘗試使用Ngrx進行函數式編程。
這篇文章總結了很多Ngrx優缺點,其中我覺得比較Ngrx顯著的優點是
數據層不僅相對于component獨立,也相對于框架獨立,便于移植到其他框架
數據單向流動,便于追蹤
Ngrx的缺點也很明顯
實現同樣功能,代碼量更大,對于簡單程序而言使用Immutable過度設計,降低開發效率
FP思維和OOP思維不同,開發難度更高
參考資料
Immutability vs Encapsulation in Angular Applications
whats-the-difference-between-markforcheck-and-detectchanges
Angular 也走 Redux 風 (使用 Ngrx)
Building a Redux application with Angular 2
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83692.html
摘要:知乎專欄前端給不了解前端的同學講前端掘金前端夠得到安全跨站請求偽造掘金前端面試問題持續更新掘金向核心貢獻代碼的六個步驟基于的仿音樂移動端個人文章用構建組件網易嚴選感受開發已完結掘金英文 2017-09-23 前端日報 精選 [譯] 網絡現狀:性能提升指南前端夠得到Web安全3--點擊劫持/UI-覆蓋攻擊React, Jest, Flow, Immutable.js將改用MIT開源協議N...
摘要:在年成為最大贏家,贏得了實現的風暴之戰。和他的競爭者位列第二沒有前端開發者可以忽視和它的生態系統。他的殺手級特性是探測功能,通過檢查任何用戶的功能,以直觀的方式讓開發人員檢查所有端點。 2016 JavaScript 后起之秀 本文轉載自:眾成翻譯譯者:zxhycxq鏈接:http://www.zcfy.cc/article/2410原文:https://risingstars2016...
摘要:這里引出了一個概念,就是數據流這個概念,在項目中我將所有數據的操作都成為數據的流動。 最近重構了一個項目,一個基于redux模型的react-native項目,目標是在混亂的代碼中梳理出一個清晰的結構來,為了實現這個目標,首先需要對項目的結構做分層處理,將各個邏輯分離出來,這里我是基于典型的MVC模型,那么為了將現有代碼重構為理想的模型,我需要做以下幾步: 拆分組件 邏輯處理 抽象、...
摘要:前端日報精選大前端公共知識梳理這些知識你都掌握了嗎以及在項目中的實踐深入貫徹閉包思想,全面理解閉包形成過程重溫核心概念和基本用法前端學習筆記自定義元素教程阮一峰的網絡日志中文譯回調是什么鬼掘金譯年,一個開發者的好習慣知乎專 2017-06-23 前端日報 精選 大前端公共知識梳理:這些知識你都掌握了嗎?Immutable.js 以及在 react+redux 項目中的實踐深入貫徹閉包思...
摘要:的優勢保證不可變每次通過操作的對象都會返回一個新的對象豐富的性能好通過字典樹對數據結構的共享的問題與原生交互不友好通過生成的對象在操作上與原生不同,如訪問屬性,。 Immutable.js Immutable的優勢 1. 保證不可變(每次通過Immutable.js操作的對象都會返回一個新的對象) 2. 豐富的API 3. 性能好 (通過字典樹對數據結構的共享) Immutab...
閱讀 3729·2021-11-24 09:39
閱讀 2610·2019-08-30 15:54
閱讀 1149·2019-08-30 13:01
閱讀 3429·2019-08-28 18:30
閱讀 1623·2019-08-26 17:44
閱讀 3591·2019-08-26 11:31
閱讀 2413·2019-08-26 10:40
閱讀 1239·2019-08-26 10:27