Angular2 中那些我看不懂的地方
博客停更了近 3 個月,實在是愧對很多在微博上推薦的同學。因為最近大部分時間都投入在公司里一個比較復雜的項目中,直到本周才算正式發布,稍得解脫。
說這個項目復雜,不僅是因為需求設計復雜,更是因為在這個項目里我們使用了很多新技術 —— Angular2(是的,beta 版),Webpack2(是的,beta 版),RxJS(這兩個 RxJS1 、 RxJS2 傻傻分不清楚)還有 TypeScript。
關于為什么一個大型線上項目會選擇這么冒進的技術選型這里不多做評價,只能說最終團隊還是成功將整個產品發布到了線上,我也對 Angular2 這個全新的 Angular 有了更全面的認識。
這篇文章不想對比 React 和 Angular2(不要急,這些東西肯定會出現在項目總結里),而是想站在熟悉 React + Redux 開發模式的前端工程師角度說一說 Angular2,尤其是那些我不太理解的地方。
劇透:這篇文章無意引起罵戰,因此如果你看到我哪里寫的不對,請直接在評論區里糾正。
看不懂的依賴注入
我知道這其實不僅僅針對 Angular2,早在 Angular1 中就存在依賴注入的概念,甚至這還是 Angular 引以為傲的特性。每當談到依賴注入的時候,我就看到一幫 OOP(以 Java 為首)愛好者眼里閃爍著奇異的光芒。
然而依賴注入的問題一直困擾著我,倒不是說不理解依賴注入的使用方法或是原理,而是不理解 為什么我的代碼里需要依賴注入?
當我把這個問題拋給項目組中強推 Angular 的同學時,他也一時語塞。
在 Angular2 中,一個常見的依賴注入是注入一個 Http 對象,用于發送請求。
import {Component, OnInit} from 'angular2/core';
import {Http} from 'angular2/http';
@Component({
selector: 'test',
providers: [Http]
})
class Test implements OnInit {
constructor(private http: Http) {
}
ngOnInit() {
this.http.get('/api/user.json').map(res => res.json()).subscribe(result => {
// 這里拿到請求的結果
});
}
}
上述代碼是在 Angular 中使用內置 Http 模塊發送請求的一段示例代碼。具體依賴注入發生在 constructor 函數中。按照 Angular 宣傳的那樣,你只用簡單的在 constructor 中聲明這些參數,Angular 將自動為你處理依賴注入的問題。
注意到我們要使用 Http 來發請求,首先需要從 angular2/http 中將 Http import 進來,然后需要在組件的配置中添加對應的 providers,最后還需要在 constructor 中聲明一個參數。
這個時候我其實是有點懵逼的,因為在一般應用里,發請求大概是這樣的。
var request = require('superagent');
function getUser() {
return request.get('/api/user.json').end((err, res) => {
// 這里是拿到請求的結果
});
}
為了公平起見,我還是假裝依賴了一個第三方的庫來實現 Ajax 請求。在這份代碼里沒有什么高大上的依賴注入,更沒有什么 constructor,只有簡單的引入并使用。
所以我不太明白,Angular 中的依賴注入究竟在多大程度上發揮著作用?
語法糖、語法糖和語法糖
很多人在第一眼看到 React 的時候,都在叫囂 JSX 是「在 JS 里嵌入 HTML」,是邪門歪道,吃棗藥丸。我只想說,相比于 Angular2 里的這些語法糖,JSX 簡直就是 JavaScript 里的純情小處男。
以下引用部分 Angular2 文檔中關于模板語法的部分:
Expanding *ngFor
The *ngFor undergoes a similar transformation. We begin with an *ngFor example:
&lt;hero-detail *ngFor="#hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detail>
Here's the same example after transporting the ngFor to the template directive:
&lt;hero-detail template="ngFor #hero of heroes; trackBy:trackByHeroes" [hero]="hero"></hero-detail>
And here it is expanded further into a <template> tag wrapping the original <hero-detail> element:
&lt;template ngFor #hero [ngForOf]="heroes" [ngForTrackBy]="trackByHeroes"> <hero-detail [hero]="hero"></hero-detail> &lt;/template>
讀完之后你會發現, *ngFor 這個指令,原來是個語法糖。好嘛,其實語法糖在很多框架設計里都有啊。但是當你展開這個語法糖之后,你會發現這個語法糖其實還是另一個語法的語法糖。這也就是說, *ngFor 是語法糖的語法糖。
這種例子在 Angular2 中還有不少,任何一個正常的 JavaScript 開發者如果不想把自己逼瘋的話,都會喜歡一個原始的 for 循環,或者一個 [].map 吧……
看到某篇文章爭論說 Angular2 的模板用的是 「Valid HTML」,我只想說管你 Valid 不 Valid,臣妾看不懂啊。
模板里面到底發生了什么
其實剛開始寫 Angular 應用的時候,心里還是對雙向綁定挺期待的。因為在 React + Redux 架構里面,表單處理永遠都是一個痛點(這里安利一下自己寫的 redux-form-utils 良心工具,線上產品背書)。但是等我真的在寫 Component 中的 template 時,就開始慢慢崩潰了。
首先最崩潰的是,我沒辦法在模板中斷點調試(還是我沒有找到正確的方法),這導致在一些模板中存在復雜邏輯的場景我只能默默的在心里推倒運行的過程。
然后是在模板里面,你還是可以寫大量的邏輯,比如這樣
@Component({
template: `
<ul #listEl [class.rendered]="listEl.rendered">
<li *ngFor="#item of list; if(last) {listEl.rendered = true;}" (click)="list.push(Math.random()); $event.stopPropagation(); $event.preventDefault(); // Do other fancy stuff">
{{ item.text }}
</li>
</ul>
`,
})
這些在模板中的代碼都是完全合法的 Angular2 語法。有人可能覺得,允許你這么寫不代表你一定要這么寫。事實上,我已開始很多組件都是這么寫的,因為很快,因為網上很多 demo 也這么寫,因為不知道該怎么更好的組織代碼。
這樣下去,一個 Angular 組件中,你不僅要閱讀組件本身的各種對數據的處理,還要看模板中的各種邏輯。尤其是一些比較復雜的組件,模板和組件本身放在不同的文件中時,這種來回上下文的切換真是酸爽。
組件通信操碎了心
在 Angular 中,父組件想要傳遞一些信息給子組件,還是比較簡單的,直接使用屬性綁定即可。在子組件中稍微麻煩一點,多聲明一個 @Input。
這里還想吐槽兩句,一個組件想要引用另一個組件,要先 import 進來,然后還要加到當前組件 Component 配置的 directives 屬性中。開發過程中好多次莫名其妙的報錯都是因為忘了這一步……
// Parent.js
@Component({
selector: 'parent',
template: `
<h1>Parent</h1>
<child [data]="data"></child>
`
})
class Parent {
data = 'react';
}
// Child.js
@Component({
selector: 'child',
template: `
<h4>Child</h4>
<p>Data from parent: {{ data }}
`
})
class Child {
@Input() data;
}
這個時候問題來了,當子組件發生某些變化父組件想要知道的時候,你有這么幾種選擇。
- 在子組件中定一個 @Output 屬性,然后父組件用 (event)="handler()" 的語法來監聽這個事件
- 將父組件的一個 event handler 當做屬性傳給子組件,子組件通過調用這個方法來通知父組件(在 Redux 里,這很常見)
- 設計一個 Service 然后分別注入到父組件和子組件之中進行通信
在我開發這個項目的時候,Angular2 的官方文檔中還沒有任何教程說明組件之間溝通該如何進行(現在 有了 ),所以我果斷選擇了最 Redux 的那種。
結果發現,后來更新的官方文檔里明確說明了不提倡使用這樣的方式。
我只能說很心累……
其它
奇葩的路由設定,用 /... 定義一個非最終頁面路由,以及沒有一個全局的路由結構,讓 Angular2 里面的路由變得非常難推導。如果你不一層一層跟蹤下去,根本就無法了解整個 Angular2 應用的路由結構。
莫名其妙的報錯,而且很多錯誤都 Google 不到答案,只能 Google 到一群絕望的開發者提出絕望的問題。看看 Github 上 Angular Repo 里那些絕望的 issue,你就知道后悔當初自己為什么要選 Angular2 作為線上項目的架構了。我自己 Watch 了很多遇到的問題,然而收到最多的更新都是「+1」。
復雜的生命周期,官方文檔看了一遍又一遍,網上的例子搜了很多,又不能確定是不是最新的 API。至今沒有全部弄明白 Angular2 所有的生命周期都是干嘛用的,只用過幾個簡單的 OnInt、AfterViewInit 和 OnChanges。
小結
寫著寫著快要變成吐槽 Angular2 了,其實 Angular2 有很多設計的非常不錯的地方值得其它框架學習。比如內置的 ViewEncapsulation 解決了 CSS 沖突的問題,完善的 NgForm 系列表單指令能夠高效的解決表單校驗和提交(前提是那些語法你都學會),底層設計與 DOM 無關以便運行在 WebWorker、服務器等不同環境等等。
不管怎么樣,從項目開始的 Angular2 Alpha,用到現在的 Angular2 Beta 15,學習到了很多新知識,但學到更多的還是自己原來還有這么多不知道。
后面還是會投入更多的精力在 Redux 生態建設上,畢竟團隊內 React + Redux 已經全面開花結果,有必要尋找到 Redux 最優的開發實踐。
來自: http://undefinedblog.com/bu-yao-ba-zi-ji-xian-ding-wei-reactjs-gong-cheng-shi/