原文連接: Do you really know what unidirectional data flow means in Angular
關於單向數據流,還能夠參考這篇文章,且文中還有
youtube
視頻解析:
Angular - What is Unidirectional Data Flow? Learn How the Angular Development Mode Works, why it's important to use it and how to Troubleshoot it 。單向數據流一句話解釋就是:不要在 Angular 使用 Model 生成 View 這個過程當中再去修改 Model。
大多數架構模式是很難理解的,尤爲是在相關資料不多時那就更加頭疼,好比 Angular 的單向數據流(unidirectional data flow)文檔資料就不多,即便官方文檔上,也僅僅在 表達式指南 和 模板表達式 兩小塊中略有說起。我也不多在網上搜到比較好的解釋文章,因此寫此文是爲了給 單向數據流(unidirectional data flow) 有個詳細的解釋。html
在討論 AngularJS 和 Angular 性能異同點時,就常常會提到單向數據流模式,它也是 Angular 比 AngularJS 性能更快的緣由所在。讓咱們一塊兒看看單向數據流究竟咋回事吧!(譯者注:AngularJS 指的是 1.X 版本,2+ 版本叫 Angular,使用 TypeScript 語言重寫了框架,包括架構模式進行了從新設計,但二者形似也神似。)git
AngularJS 和 Angular 都是經過綁定來實現組件間數據通訊,好比在 AngularJS 定義一個父組件 A
:github
app.component('aComponent', { controller: class ParentComponent() { this.value = {name: 'initial'}; }, template: ` <b-component obj="$ctrl.value"></b-component> ` }); ---------------- app.component('bComponent', { bindings: { obj: '=' },
能夠看到父組件 A
有個子組件 B
,而且經過輸入綁定把父組件 A
的 value
屬性值傳給子組件 B
的 obj
屬性:web
<b-component obj="$ctrl.value"></b-component>
這和 Angular 中組件間通訊很相似了啊:express
@Component({ template: ` <b-component [obj]="value"></b-component> ... export class AppComponent { value = {name: 'initial'}; } ---------------- export class BComponent { @Input() obj;
第一件重要的事情是,Angular 和 AngularJS 都是在變動檢測(change detection)期間纔去更新綁定值。因此,當 Angular 框架在爲父組件 A
運行變動檢測時,它會更新子組件 B
的 obj
屬性:redux
bComponentInstance.obj = aComponentInstance.value;
上面過程證實了單向數據流是數據從上往下流(譯者注:即數據從組件樹的父組件往子組件流),可是 AngularJS 卻不同,它能夠容許在子組件中更新父組件的 value
屬性值:angular2
app.component('parentComponent', { controller: function ParentComponent($timeout) { $timeout(()=>{ console.log(this.value); // logs {name: 'updated'} }, 3000) } ---------------- app.component('childComponent', { controller: function ChildComponent($timeout) { $timeout(()=>{ this.obj = { name: 'updated' }; }, 2000)
上面代碼你能夠看到兩個 timeout
回調,第一個回調會更新子組件屬性,第二個回調會延遲第一個回調一秒,檢查父組件屬性是否被更新。若是你在 AngularJS 中運行上面代碼你會發現父組件屬性已經被更新了。讓咱們一塊兒看看究竟發生了什麼?架構
當第一個回調運行時,子組件 B
的 obj
屬性被更新爲 {name: 'updated'}
,而後 AngularJS 運行變動檢測。在變動檢測過程當中,AngularJS 檢測到子組件綁定屬性的值發生改變,它會更新父組件 A
的 value
屬性值。這是 AngularJS 變動檢測的內置功能。若是你在 Angular 中作一樣的事情,它僅僅更新子組件的屬性值,可是子組件的改變不會冒泡到父組件,即 Angular 不會改變父組件的屬性值。這是升級版後的 Angular 變動檢測實現,相較於 AngularJS 的最重大區別。然而,它卻困擾了我很久啊。app
然而,Angular 也一樣能夠經過子組件來更新父組件,這個機制就是輸出綁定(output binding)。能夠像這樣使用輸出綁定:框架
@Component({ template: ` <h1>Hello {{value.name}}</h1> <a-comp (updateObj)="value = $event"></a-comp> ... export class AppComponent { value = {name: 'initial'}; constructor() { setTimeout(() => { console.log(this.value); // logs {name: 'updated'} }, 3000); ---------------- @Component({...}) export class AComponent { @Output() updateObj = new EventEmitter(); constructor() { setTimeout(() => { this.updateObj.emit({name: 'updated'}); }, 2000);
我認可這個和從子組件直接更新還不太同樣,可是父組件值的確是更新了啊。很長一段時間我不明白爲什麼這沒有被看作雙向數據綁定?畢竟,數據通訊是在兩個方向進行的。
直到有一晚我讀到了 Two Phases of Angular Applications by Victor Savkin,他解釋道(譯者注:爲清晰理解,這個解釋不翻譯):
Angular 2 separates updating the application model and reflecting the state of the model in the view into two distinct phases. The developer is responsible for updating the application model. Angular, by means of change detection, is responsible for reflecting the state of the model in the view.
(譯者注:意思就是 Angular 劃分了 更新程序 model在前和 同步 model 和 view在後兩個步驟,開發者只須要關注 更新程序 model, 同步 model 和 view 由 Angular 框架負責,這個同步過程就是 變動檢測,說白了就是 更新 view。)
(譯者注:好比朋友圈點贊,首先就是手指觸發異步事件來 更新程序 model,如在 Post 組件裏給 likes 屬性加 1,而後在下一個 VM turn 時 Angular 會自動更新視圖中綁定的 likes 值, 即 同步 model 和 view,點贊數就會從 0 變爲 1。)
這讓我花費好些天才明白,所謂的使用輸出綁定機制來更新父組件並非在變動檢測中執行的:
<a-comp (updateObj)="value = $event"></a-comp>
相反,它是在變動檢測前的更新程序 model 階段執行的。因此,單向數據流的意思是指在變動檢測期間屬性綁定變動的架構。在上面 Angular 代碼示例中,不像 AngularJS,在 Angular 變動檢測期間,並無代碼去讓子組件 AComponent
去更新父組件 AppComponent
的屬性。相反,輸出綁定過程並無在變動檢測期間內運行,因此它沒有把單向數據流轉變爲雙向數據流。
另外,儘管 Angular 沒有內置機制可使得在變動檢測期間去更新父組件,然而能夠經過共享服務或廣播同步事件作到這一點。可是,因爲 Angular 框架強迫單向數據流,因此這麼作會致使ExpressionChangedAfterItHasBeenCheckedError
錯誤。想要了解致使這個錯誤的緣由和解決方案能夠參考 Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError
。
你可能知道,大多數 web 程序會設計成有兩層:視圖層和服務層。
Web 環境下視圖層主要經過 DOM 結構向用戶展現相關程序數據,在 Angular 中這一層是經過組件作的。服務層主要是處理和保存數據,正如上圖中展現的,這一層又被切分爲狀態管理和一些基礎部分,如 rest 服務或可重用工具服務(helpers)。
上文中提到的單向數據流指的是視圖層,由於它說的是組件,而組件是用來構建視圖的:
然而,在隨着實現了 redux 架構的 ngrx 引入後,這又讓人迷惑了,由於 redux 文檔上有 這麼一句(譯者注:爲清晰理解,這句不翻譯):
Redux architecture revolves around a strict unidirectional data flow.
This means that all data in an application follows the same lifecycle pattern, making the logic of your app more predictable and easier to understand…
實際上,這個嚴格單項數據流(strict unidirectional data flow)其實說的是服務層而不是視圖層。但我有時會搞混服務層和視圖層,會把 redux 的架構模式與 Angular 的結構模式聯繫起來,固然要避免搞混這兩層嗷。Redux 說的單向數據流說的是服務層,而不是視圖層嗷。 Redux 主要說的是狀態管理模塊,會把咱們上文說的雙向數據流轉變爲單向數據流。
把雙向數據流:
轉換爲單向數據流: