【推薦】給Java程序員的Angular快速指南
太長(zhǎng)不讀版:
Spring + Angular 的全棧式開發(fā),生產(chǎn)力高、入門難度低(此處省略一萬字),是 Java 程序員擴(kuò)展技術(shù)棧的上佳選擇。
如果你動(dòng)心了,接下來就是那省略的一萬字……
痛點(diǎn) – 團(tuán)隊(duì)分工與協(xié)作
在前后端分離的開發(fā)方式中,拆故事卡是個(gè)難題。
如果前后端同時(shí)工作于一張卡上,但配合不夠默契或節(jié)奏不同步,就會(huì)出現(xiàn)一方空轉(zhuǎn)的現(xiàn)象。如果前后端各一張卡,又不容易實(shí)現(xiàn)端到端驗(yàn)收,可能導(dǎo)致先做完的一方在另一個(gè)結(jié)束后還要再次返工的現(xiàn)象。而且,兩個(gè)人都要深入理解這張卡所描述的業(yè)務(wù)細(xì)節(jié),而這往往是不必要的。
更重要的是,BUG 最容易出現(xiàn)在邊界處。
業(yè)務(wù)卡不像技術(shù)卡那樣能跟其它卡片劃出明確的邊界,前后端之間必然具有千絲萬縷的聯(lián)系。這種聯(lián)系越緊密,出 BUG 的機(jī)會(huì)也就越大。
技術(shù)架構(gòu)上的挑戰(zhàn),也會(huì)反映到人員架構(gòu)上。我們?nèi)祟惒皇切庆`,無法做到心靈相通。因此前后端開發(fā)者需要對(duì)合作方所擁有的知識(shí)進(jìn)行很多主觀假設(shè)。
如果這些假設(shè)中存在錯(cuò)誤,又沒能及時(shí)溝通來消除它(甚至可能都意識(shí)不到這些假設(shè)的存在),那么 BUGs 就要登場(chǎng)了。而像業(yè)務(wù)卡這種級(jí)別的密切協(xié)作中可能隱含的假設(shè)實(shí)在太多了,除非經(jīng)過長(zhǎng)時(shí)間的磨合,否則很難消除,但大多數(shù)項(xiàng)目上可沒有那么多磨合時(shí)間。
解決方案 —— 全棧式開發(fā)
人員架構(gòu)
該如何解決呢?克服上述問題的辦法就是全棧式開發(fā)。也就是說,調(diào)整人員架構(gòu)去適應(yīng)技術(shù)架構(gòu)。
簡(jiǎn)單來說:每個(gè)人都同時(shí)寫前端和后端。他不必是前端專家也不必是后端專家,但是兩邊都要會(huì)寫。他的關(guān)注點(diǎn)不是技術(shù)知識(shí),而是業(yè)務(wù)知識(shí)。他的工作目標(biāo)是貫穿前后端的價(jià)值流,對(duì)單個(gè)故事進(jìn)行端到端交付。
但是,要如何克服實(shí)現(xiàn)中遇到的技術(shù)難題以及保障代碼質(zhì)量呢?那就要靠團(tuán)隊(duì)中的技術(shù)專家了。
總體來說,全棧式團(tuán)隊(duì)的人員架構(gòu)就是大量全棧業(yè)務(wù)工程師 + 少量技術(shù)專家。當(dāng)然,技術(shù)專家不一定要安排單獨(dú)的人擔(dān)任,只要技術(shù)滿足要求,也可以由某位全棧工程師兼任,只是他做計(jì)劃時(shí)要留出做技術(shù)支持的時(shí)間。
通過 Code Review、Pair 等敏捷實(shí)踐,技術(shù)專家可以起到團(tuán)隊(duì)放大器的作用,讓整個(gè)團(tuán)隊(duì)的生產(chǎn)力翻倍。
個(gè)人工作流
作為全棧工程師,你首先要對(duì)一個(gè)業(yè)務(wù)故事進(jìn)行建模,包括業(yè)務(wù)模型、視圖模型、領(lǐng)域模型、存儲(chǔ)模型等,建模的過程也就是你理解業(yè)務(wù)的過程。這時(shí)候要注意多和 BA、UX、DBA 等溝通,以確保你的理解不存在方向性錯(cuò)誤,不要太沉迷細(xì)節(jié),防止見木不見林。
單源建模的優(yōu)點(diǎn)是這些模型之間很容易保持一致,這無論是對(duì)前期開發(fā)還是對(duì)后期維護(hù)都是有幫助的。
建模完畢之后,就要開始設(shè)計(jì)前后端之間的接口了。接口是前后端分離式架構(gòu)中最容易開裂的地方,也是對(duì)未來的演化影響最大的地方之一。它很重要,但也不必小心翼翼的 —— 全棧工程師對(duì)接口變化的適應(yīng)能力要強(qiáng)大得多。因?yàn)榻涌诘奶峁┓胶拖M(fèi)方都是你,信息非常透明,不存在任何額外的假設(shè)。對(duì)不完美的接口,你可以在后續(xù)開發(fā)過程中迭代好幾個(gè)版本來把它打磨到最理想的形態(tài),改接口將不再沉重和危險(xiǎn)。
接口設(shè)計(jì)完之后,有兩種路徑,取決于界面和后臺(tái)邏輯的特點(diǎn)。
如果對(duì)業(yè)務(wù)理解還不是很有信心,那就先用 Mock 的方式把前端寫出來,然后把這個(gè) Mock 版當(dāng)做可執(zhí)行的原型去跟 BA、QA,甚至客戶進(jìn)行實(shí)際操作演示,用可操作原型來驗(yàn)證你對(duì)業(yè)務(wù)的理解。對(duì)一般粒度的故事卡,線框圖級(jí)的可操作原型通常能在半天內(nèi)完成。通過原型盡早發(fā)現(xiàn)錯(cuò)誤,可以避免以后沉重的返工。而且,這是一個(gè)可演化原型,不是一次性原型,不會(huì)浪費(fèi)掉。
如果后端很容易實(shí)現(xiàn)(但先不必做優(yōu)化工作),那么就可以不必 Mock,先初步完成后端開發(fā),并讓前端直接對(duì)接真實(shí)的后端。先拿這個(gè)比 Mock 版原型更逼真一點(diǎn)的原型串起流程,然后再進(jìn)行優(yōu)化和打磨工作。
在整個(gè)過程中,你可以根據(jù)不同的需要,來與不同的技術(shù)專家進(jìn)行 Pair,并且你最終的代碼也會(huì)在例行 Code Review 中得到前端專家、后端專家、DBA、DevOps 專家等人的點(diǎn)評(píng)和改進(jìn),不必?fù)?dān)心自己在單項(xiàng)技術(shù)上的短板影響交付。
全棧的挑戰(zhàn)
全棧固然美好,但也要迎接很多挑戰(zhàn),而 Angular 會(huì)幫你分擔(dān)這些痛苦。
首先遇到的挑戰(zhàn)是語言切換
前后端 JavaScript 全棧固然在特定場(chǎng)景下有效,但是在很多企業(yè)應(yīng)用中是遠(yuǎn)遠(yuǎn)不夠的。至少到目前為止,企業(yè)應(yīng)用還主要是 Java 的天下。本文所討論的也都是 Java + JavaScript 的全棧。
我們都知道,Java 和 JavaScript 之間的差異就像雷鋒和雷峰塔之間的差異。Java 程序員通常很難適應(yīng) JavaScript,不過現(xiàn)在有了更像 Java 的 TypeScript。而 Angular 就是原生基于 TypeScript 的框架,稍后我會(huì)做一個(gè)摘要講解,你會(huì)發(fā)現(xiàn)自己很熟悉它的味道。
(圖片來自: http://t.cn/RobG5nA )
其次是基礎(chǔ)設(shè)施
基于 JRE 的構(gòu)建體系和基于 NodeJS 的構(gòu)建體系看似差異很大,實(shí)際上卻有很大程度的相似性。但前端兩年一換代的瘋狂迭代,以及層出不窮的新名詞、新工具,仍然難免會(huì)讓后端心生恐懼。不過不用擔(dān)心,Angular 替你封裝了一切,你只需要裝上 NodeJS 環(huán)境和 Angular CLI 就可以了。你不需要關(guān)心它封裝了哪些第三方工具,至于今后的工具鏈怎么瘋狂迭代,那都是 Angular 開發(fā)組需要操心的事。
最后是最佳實(shí)踐
前后端從表面上看差異很大 —— 前端輕靈,后端穩(wěn)重。
但在我看來它們很少存在本質(zhì)性的差異,更像是不同的社區(qū)文化導(dǎo)致的結(jié)果。而在更高的層次上看,兩邊的技術(shù)具有很大的相似性。無論是函數(shù)式編程還是工程化開發(fā),都不是某一方所特有的,而是 IT 領(lǐng)域的共同資產(chǎn)。況且,它們還一直在相互影響,相互滲透 —— 這兩年后端變得越來越輕靈,而前端變得越來越工程化。長(zhǎng)遠(yuǎn)來看,文化合流是必然的趨勢(shì)。
事實(shí)上,前后端很多優(yōu)秀設(shè)計(jì)和最佳實(shí)踐都是殊途同歸的。像 Spring 和 Angular,它們都采用了久經(jīng)考驗(yàn)的面向?qū)ο蠓妒剑欢际褂靡蕾囎⑷爰夹g(shù)進(jìn)行解耦;都擁抱函數(shù)式編程;都提供了豐富的 AOP 支持等。雖然細(xì)節(jié)上各有千秋,但僅從代碼上就能感受到它們之間的相似性。
我該怎么辦?
聽完這些,你是否已經(jīng)蠢蠢欲動(dòng)?接下來,就跟我開始 Angular 之旅吧。
語言 – TypeScript
Angular 使用 TypeScript 作為主要開發(fā)語言。如果你還不熟悉 TypeScript,那可以把它看做 Java 和 JavaScript 的混合體。TypeScript 是 ES6 的超集,這就意味著,任何有效的 ES6 語法都同樣是有效的 TypeScript 語法。
事實(shí)上,從 Java 出發(fā)學(xué) TypeScript,可能比從 ES5/6 學(xué) TypeScript 還要簡(jiǎn)單一些。不過,對(duì)于 Javaer 來說,學(xué)習(xí) TypeScript 時(shí)有一些重要的不同點(diǎn)要特別注意。
TypeScript 的類型只存在于編譯期
TypeScript 的一個(gè)首要設(shè)計(jì)約束就是要兼容 ES5/6,因此不能隨意增加基礎(chǔ)設(shè)施,而像 Java 這種級(jí)別的類型支持在原生 JavaScript 中是根本不存在的。
你可以把 TypeScript 的類型看做僅僅給編譯器和 IDE 用的。因此,在運(yùn)行期間沒有任何額外的類型信息(只有 ES5 固有的那一小部分),像 Java 那樣完善的反射機(jī)制是很難實(shí)現(xiàn)的(可以用裝飾器/注解實(shí)現(xiàn),但比較繁瑣)。
TypeScript 的裝飾器 vs. Java 的注解
TypeScript 的裝飾器和 Java 的注解在語法上很相似,但其實(shí)在語法含義上有著本質(zhì)的區(qū)別。TypeScript 的裝飾器是個(gè)函數(shù),而 Java 的注解是個(gè)數(shù)據(jù)。語法上,裝飾器名字后面必須帶括號(hào),不能像注解那樣省略。
不過,在 Angular 中,TypeScript 裝飾器的實(shí)際用途就是為類或?qū)傩蕴砑幼⒔舛选R虼耍行┪恼轮校ㄔ缙诘墓俜轿臋n中,用的都是注解的說法。當(dāng)然,以后寫新文章還是都用裝飾器吧。
類與接口
TypeScript 中的類和 ES6 中的類幾乎是一樣的,和 Java 中的類也很相似。
接口則不同,我們前面說過,TypeScript 中的類型信息只存在于編譯期,而接口作為“純粹的”類型信息,也同樣只存在于編譯期。也就是說,在運(yùn)行期間你無法判斷某個(gè)對(duì)象的類是否實(shí)現(xiàn)了某個(gè)接口。在 Angular 中,實(shí)際上使用的是暴力探測(cè)法來判斷的:查找這個(gè)接口中規(guī)定的方法(只匹配名稱),如果存在,則認(rèn)為實(shí)現(xiàn)了這個(gè)接口。
這也意味著,你就算不顯式 implements 接口,但只要聲明了其中的方法,Angular 也會(huì)正確的識(shí)別它。但這不是一個(gè)好習(xí)慣,你應(yīng)該始終顯式 implements 接口,刪除時(shí)也要同時(shí)刪除接口聲明和對(duì)應(yīng)的方法。不過也不用擔(dān)心,Angular 自帶的 lint 工具會(huì)幫你檢查是否有忘了顯式 implements 接口,多注意提示就可以了。
接口是給編譯器和 IDE 看的,這很有用。比如,我們可以在 IntelliJ/WebStorm 中聲明某個(gè)類實(shí)現(xiàn)了一個(gè)接口,然后在這個(gè)類名上按 alt-enter ,就會(huì)出現(xiàn) “Implement interface XXX” 菜單 —— 就像 Java 中一樣。事實(shí)上,一些 IDE 對(duì) TypeScript 的支持程度已經(jīng)接近 Java 了:代碼提示、重構(gòu)、類型檢查、簡(jiǎn)短寫法提醒等,應(yīng)有盡有。
值得注意的是:你也可以 implement 一個(gè)類,而不僅是 extends 它,也就是說類可以在很多場(chǎng)景下代替接口!Angular 風(fēng)格指南提出,“考慮在服務(wù)和可聲明對(duì)象(組件、指令和管道)中用類代替接口”。因?yàn)檫\(yùn)行期間接口不存在,所以在 Angular 中不能把接口用作依賴注入的 Token,也就不能像 Java 中那樣要求注入一個(gè)接口,并期待框架幫你找出實(shí)現(xiàn)了這個(gè)接口的可注入對(duì)象,但類存在,因此,上述場(chǎng)景下要盡量用抽象類來代替接口。
鴨子類型
為了支持 JavaScript 的動(dòng)態(tài)性和遺留代碼,TypeScript 的類型匹配要比 Java 寬松不少。比如,如果兩個(gè)類(或接口)的屬性和方法(名稱、類型)都完全一致,那么即使它們沒有繼承關(guān)系,也可以相互替代(但如果類有私有屬性,則不能,就算兩者完全一樣也不行)。表面上看這可能過于寬松了,但在實(shí)際開發(fā)中還是很有用的,使用中要注意突破 Java 固有思維的限制。
在 TypeScript 中還支持可選屬性( name?: Type
),也就是說如果兩個(gè)類的差別僅僅在可選屬性上,那么它們也是可以相互替代的。
字面量與匿名類型
TypeScript 在某些方面可能更符合你對(duì) Java “應(yīng)該是什么樣子”的期待,至少在我看來是這樣。要聲明一個(gè)匿名對(duì)象、匿名數(shù)組型變量?直接寫出來就好了 const user = {name: 'tom', age: 20}
。除此之外,它還能聲明匿名類型 let user: {name: string, age: number} = ...
。
當(dāng)然,也不能濫用它們。對(duì)于一次性使用或暫時(shí)一次性使用的變量或類型,用字面量和匿名類型很方便,可讀性也好,但是如果它要使用兩次以上,那就該重構(gòu)成正式的類型了。
any
TypeScript 中的 any
大致相當(dāng)于 Java 中的 Object
,如果你看到通篇 Object
的 Java 代碼你會(huì)不會(huì)想罵街? any
也一樣。不必完全禁止 any
,但如果你要使用 any
,請(qǐng)務(wù)必先想清楚自己要做什么。
void
如果你在 Java 中經(jīng)常使用 void
,那就遵循同樣的原則用在 TypeScript 中。在 TypeScript 中,當(dāng)你不聲明函數(shù)的返回類型時(shí),它會(huì)返回自動(dòng)推斷的類型(沒有明確的 return value
語句時(shí)會(huì)推斷為 undefined
類型),如果你不想返回任何值,那么請(qǐng)把返回類型指定為 void
來防止別人誤用。
this
JavaScript 中的 this
是個(gè)奇葩。雖然這是函數(shù)式語言中的標(biāo)配,但從語言設(shè)計(jì)上真是讓人忍不住吐槽。要是能像 Groovy 那樣分出 this
/ owner
/ delegate
就好了。
吐槽歸吐槽,對(duì)于 Java 程序員,該怎么避免自己踩坑呢?很簡(jiǎn)單:對(duì)普通函數(shù),任何涉及到 this
的地方都用箭頭函數(shù) ()=>
,而不要用普通的 function foo()
,因?yàn)榍罢呤翘婺憬壎ê昧朔现庇X的 this
的;對(duì)方法,不要把任何涉及到 this 的方法當(dāng)作函數(shù)指針傳給別人,但可以在模板中自由使用。在 Angular 中,這兩條原則可以幫你回避掉絕大部分 this 錯(cuò)誤。更多的細(xì)節(jié)可以先不管,隨著使用經(jīng)驗(yàn)的增加,你會(huì)逐漸弄明白這些規(guī)則的。
其它
以上這些是開發(fā)中常遇到的注意事項(xiàng),其它的特性我就不一一列舉了,請(qǐng)自行參考 TypeScript 的官方文檔。
范式與模型
MVVM
Angular 的基本編程模型是 MVVM,你可以把它看做 MVC 的一個(gè)變種。事實(shí)上,這是一個(gè)很符合直覺的模型:你看到一個(gè)頁面,先在大腦中抽取出它的信息架構(gòu)(屬性)和操作(方法),定義好它們之間的邏輯關(guān)系和處理流程,這就是視圖模型(VM)。你把它們落實(shí)到代碼,變成內(nèi)存對(duì)象,然后 Angular 就會(huì)幫你把它和頁面(View)關(guān)聯(lián)起來。你不懂怎么操作 DOM?沒關(guān)系,你只要會(huì)操作內(nèi)存對(duì)象就可以了,這應(yīng)該是你非常擅長(zhǎng)的吧?剩下的那些臟活兒 Angular 都會(huì)幫你搞定。
不過,Angular 關(guān)心的只是“要有” VM,至于你如何生成這個(gè) VM,它并不會(huì)做任何假設(shè)和限制。
自由混搭與切換
你想怎么生成 VM?
- 像后端控制器那樣直接寫在組件中?沒問題!
- 像后端那樣委托給服務(wù)?沒問題!
- 像 Redux 那樣委托給單一 Store?沒問題!
- 像 Java 8 Stream 那樣用流水線生成?沒問題!
- 自己幾乎不處理,完全委托給后端 API?沒問題!
這么多方式各有不同的適用場(chǎng)景,但也不必過早擔(dān)心如何選型。只要你的組件設(shè)計(jì)合理(職責(zé)分明、接口明確等),那么在這些方式之間切換,或者混用它們,都不會(huì)很難。
作為起點(diǎn),可以先直接寫在組件中,然后按需重構(gòu)成服務(wù),服務(wù)中可以直接寫代碼,也可以實(shí)現(xiàn) Redux 風(fēng)格的單一 Store,或者用 RxJS 寫流水線。
RxJS
在 Angular 開發(fā)人員的成長(zhǎng)過程中,有一個(gè)很重要的坎就是 RxJS,它的背后是 FRP(函數(shù)響應(yīng)式編程)范式。不過對(duì)于 Javaer 來說,它的門檻并不高。如果你會(huì)用 RxJava / RxGroovy 等 ReactiveX 族的任何一個(gè)庫,那么你幾乎可以不用專門再學(xué),它們都是同一個(gè)大家族,編程范式甚至部分操作符的名稱都一樣,稍微對(duì)比一下差異就可以了。如果不會(huì),請(qǐng)繼續(xù)往下讀(以下的討論也適用于 RxJava 等,不過我文中只用 RxJS 舉例)。
RxJS 是一種 FRP(函數(shù)響應(yīng)式編程)庫,它同時(shí)具有函數(shù)式編程和響應(yīng)式編程的優(yōu)點(diǎn)。
如果你會(huì)用 Java 8 Stream,那么也有很多知識(shí)可以復(fù)用到這里。相對(duì)于 Java 8 Stream,RxJS 的限制稍微寬松一些,但我建議你仍然按照 Java 那種嚴(yán)格的方式使用它(比如不要對(duì)流外的變量賦值)。
所謂響應(yīng)式編程,我們可以把它想象成一條流水線,流水線上不斷傳送待加工的材料(原料、半成品、成品等),流水線上每個(gè)工序的工人負(fù)責(zé)對(duì)傳送到眼前的材料進(jìn)行一定的處理(做出響應(yīng)),然后放回流水線,接著它就會(huì)被傳送到下一個(gè)工序。
設(shè)計(jì)上,每個(gè)工序的職責(zé)都應(yīng)該是明確而單一的,這樣才能達(dá)到最高的效率和流水線的可定制性。
把這些概念映射到 RxJS,流水線就是 Observable(可觀察對(duì)象),工序就是 operator(操作符),材料就是傳給每個(gè) operator 的參數(shù)。
是不是感到很熟悉?沒錯(cuò),它跟 MessageQueue 是一樣的模型,只是應(yīng)用在不同的層次而已。在編程領(lǐng)域,這種現(xiàn)象隨處可見,善于發(fā)現(xiàn)并掌握這種現(xiàn)象,是你作為資深程序員能實(shí)現(xiàn)快速跨領(lǐng)域?qū)W習(xí)的根本保障。
相對(duì)于 Java 8 Stream,RxJS 比較特別的一點(diǎn)是它完全屏蔽了同步和異步之間的差異。也就是說,其中的 operator 不知道也不需要關(guān)心這個(gè)數(shù)據(jù)是同步傳過來的還是異步傳過來的。只要你遵循一些顯而易見的原則,你就可以一直用同步方式給數(shù)據(jù),之后即使要突然改成異步,原有的代碼也不會(huì)被破壞。
事實(shí)上,我在 Angular 開發(fā)中經(jīng)常利用這種特性來加速開發(fā)。比如假設(shè)我最終需要從后端 API 獲取某些信息,在這個(gè) API 開發(fā)好之前,我可以先在前端模擬出響應(yīng)結(jié)果,進(jìn)行后續(xù)開發(fā)。這時(shí)候,如果我用 Observable 的方式聲明數(shù)據(jù)源,那么雖然我目前用同步的方式提供數(shù)據(jù),但是將來我可以直接切換成 HTTP 數(shù)據(jù)源,而不用擔(dān)心破壞現(xiàn)有代碼。
細(xì)部原理
宏觀上的要點(diǎn)已經(jīng)講完了,接下來我們快速過一遍微觀的。我只講要點(diǎn),要想深入細(xì)節(jié)請(qǐng)參閱文中給出的參考資料。
Angular 模塊
Angular 模塊不同于 JavaScript 模塊,它是一個(gè)架構(gòu)級(jí)的基礎(chǔ)設(shè)施,用來對(duì)應(yīng)用進(jìn)行宏觀拆分,硬化邊界,防止意外耦合。
模塊的劃分主要基于業(yè)務(wù)領(lǐng)域的邊界,而在開發(fā)組織形式上,也要和模塊劃分方式相互對(duì)齊,盡量讓每個(gè)模塊都有明確的負(fù)責(zé)人。
參見 https://angular.cn/guide/ngmodules 。
路由
傳統(tǒng)的路由功能完全是由后端提供的,但是在單頁面應(yīng)用中,在頁面中點(diǎn)擊 URL 時(shí),將會(huì)首先被前端程序攔截,如果前端程序能處理這個(gè) URL,那就會(huì)直接在前端處理,而不會(huì)向后端發(fā)送這個(gè)請(qǐng)求。
前端可以根據(jù)這個(gè) URL 修改視圖,給用戶與后端路由一樣的結(jié)果,但省去了網(wǎng)絡(luò)交互的過程,因此會(huì)顯得非常快捷。
路由是業(yè)務(wù)功能的天然邊界,善用路由對(duì)于改善代碼結(jié)構(gòu)和可維護(hù)性是很有幫助的。
在 Angular 中,路由還同時(shí)提供了惰性加載等特性,因此,早期對(duì)路由進(jìn)行合理規(guī)劃非常重要。不過也不用過于擔(dān)心,Angular 中重新劃分路由的代價(jià)并不高。
參見 https://angular.cn/guide/router#appendix-emlocationstrategyem-and-browser-url-styles 。
模板與視圖
你可以把模板看做 JSP,主要區(qū)別是 JSP 是后端渲染的,每次生成都需要一次網(wǎng)絡(luò)交互,而模板是前端渲染的,在瀏覽器中執(zhí)行模板編譯成的 JS 來改變外觀和響應(yīng)事件。
模板語法
雖然看起來奇怪,但 [prop]
、 (click)
、 *ngFor
等模板語法中的特殊符號(hào)都是完全合法的 HTML 屬性名,實(shí)際上,屬性名中只禁用各類空白字符、單雙引號(hào)等少數(shù)幾個(gè)顯而易見的無效字符(正則: [^\t\n\f \/>"'=]
)。
參見 https://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#syntax-attribute-name 。
屬性與……屬性
由于歷史原因,英文的 Attribute 和 Property 都被譯為屬性,然而兩者是截然不同的。Angular 中的常規(guī)綁定語法針對(duì)的都是 Property,只有 [attr.xxx]
綁定針對(duì)的是 Attribute。
參見 https://angular.cn/guide/template-syntax#html-attribute-vs-dom-property 。
組件與指令
你可以把組件看做后端模板中的 taglib,區(qū)別是它們運(yùn)行在瀏覽器中而不是服務(wù)端。組件與指令在用途上的區(qū)別是,組件充當(dāng)搭建界面的磚塊,它的地位和 HTML 元素并無區(qū)別;而指令用于為 HTML 元素(包括組件)添加能力或改變行為。
所以,組件中不應(yīng)該操縱 DOM,只應(yīng)該關(guān)注視圖模型,而指令負(fù)責(zé)在模型和 DOM 之間建立聯(lián)系。指令應(yīng)該是單一職責(zé)的,如果需要完成多個(gè)職責(zé),請(qǐng)拆成多個(gè)指令附加到同一個(gè)元素上。
服務(wù)與依賴注入
Angular 的服務(wù)與依賴注入和 Spring 中的很像,主要的區(qū)別是 Angular 是個(gè)樹狀的多級(jí)注入體系,注入器樹是和組件樹一一對(duì)應(yīng)的,當(dāng)組件要查找特定的服務(wù)時(shí),會(huì)從該組件逐級(jí)向上查找,直到根部。
這實(shí)際上是職責(zé)鏈模式。當(dāng)前組件找不到某個(gè)服務(wù)時(shí),就會(huì)委托給其父節(jié)點(diǎn)來查找。和策略模式結(jié)合使用,組件就可以通過自己提供一個(gè)服務(wù)來替換父組件提供的服務(wù),實(shí)現(xiàn)一種支持默認(rèn)處理的邏輯。
參見 https://angular.cn/guide/hierarchical-dependency-injection 。
表單與驗(yàn)證
在前端程序中,驗(yàn)證主要是為了用戶友好性,而不是安全。安全是后端的工作,不能因?yàn)榍岸俗隽蓑?yàn)證而放松。
Angular 對(duì)表單提供了非常強(qiáng)力的支持。如果你的應(yīng)用中存在大量表單、大型表單、可復(fù)用表單或交互比較復(fù)雜的表單,那么 Angular 的表單功能可以為你提供強(qiáng)大的助力。
Angular 的表單提供了不同層級(jí)的抽象,讓你可以在開發(fā)中輕松分離開顯示、校驗(yàn)、報(bào)錯(cuò)等不同的關(guān)注點(diǎn)。也讓你可以先用文本框快速搭出一個(gè)表單,將來再逐個(gè)把這些文本框替換成自定義編輯框,而不會(huì)破壞客戶代碼。
參見 https://angular.cn/guide/user-input 。
測(cè)試
Angular 對(duì)測(cè)試的支持非常全面,可以實(shí)現(xiàn)各個(gè)不同層次的測(cè)試。
但是不要因?yàn)槟玫桨堰@么好用的錘子就滿世界敲。要根據(jù)不同的價(jià)值需求去決定測(cè)什么不測(cè)什么。
別忘了每個(gè) Angular 的類,無論服務(wù)、組件、指令還是管道等,都是 POJO,你可以用測(cè) POJO 的方式測(cè)試它們,得到毫秒級(jí)反饋,而且這往往會(huì)更高效。
參見 https://angular.cn/guide/testing 。但要記住:雖然 Angular 支持這么多種方式,但你不一定要用到這么多種方式。
安全
在 Angular 中,你不會(huì)無意間造成安全隱患。只要注意一點(diǎn)就夠了: DomSanitizer.bypassSecurityTrust*
要慎用,務(wù)必確保傳給它的東西不可能被攻擊者定制,必要時(shí)請(qǐng)找安全專家推演。參見 https://angular.cn/guide/security#sanitization-and-security-contexts。
如果你在發(fā)起 POST 等請(qǐng)求時(shí)收到了 403 錯(cuò)誤,那可能是因?yàn)楹蠖碎_啟了 CSRF 防護(hù)。Angular 內(nèi)置了一個(gè)約定 —— 如果服務(wù)端 csrf token 的cookie名是 XSRF-TOKEN
,并且能識(shí)別一個(gè)名叫 X-XSRF-TOKEN
的請(qǐng)求頭,那么它就會(huì)自動(dòng)幫你完成 CSRF 驗(yàn)證。當(dāng)然,你也可以自定義這些名稱來適配后端,參見 https://angular.cn/guide/http#configuring-custom-cookieheader-names 。
跨域與反向代理
本地開發(fā)時(shí),前端有自己的服務(wù)器,顯然無法與后端 API 服務(wù)器運(yùn)行在同一個(gè)端口上,這樣就導(dǎo)致了跨域問題。要解決跨域問題,主要有 CORS 和反向代理這兩種方式。CORS 是標(biāo)準(zhǔn)化的,但是受瀏覽器兼容性的影響較大;而反向代理則通過把 API “拉”到前端的同一個(gè)域下,從根本上消除了跨域訪問。
開發(fā)時(shí),Angular CLI 內(nèi)置了對(duì)反向代理的支持;部署時(shí),各個(gè)主流 Web 服務(wù)器都能很好地支持反向代理。
一般項(xiàng)目中建議還是優(yōu)先使用反向代理的方式。
(圖片來自: http://t.cn/RgsWKEJ )
雜談
你不必寫 CSS
很多后端初學(xué)前端時(shí)會(huì)被卡在 CSS 上,在心里喊一句 WTF。但實(shí)際上,在團(tuán)隊(duì)開發(fā)中,你可能根本不必寫 CSS。
現(xiàn)在已經(jīng)有了很多現(xiàn)成的 CSS 庫,比如已經(jīng)熟透的 Bootstrap,還有后起之秀 Material Design、Ant Design 等等。你只要能根據(jù)其表達(dá)的視覺含義,正確套用它們定義的 CSS 類就夠了。盡量不要自己手寫 CSS,否則可能反倒會(huì)給將來的頁面美化工作帶來困擾。
選好了基礎(chǔ)框架,并且和 UX 對(duì)齊之后,團(tuán)隊(duì)中只需要一個(gè) CSS 高手就能實(shí)現(xiàn)所有的全局性設(shè)計(jì)規(guī)則。對(duì)于全棧工程師來說,充其量只有對(duì)當(dāng)前頁面中的少量元素進(jìn)行定制時(shí)才需要寫 CSS,況且還可以通過找人 pair 來解決偶爾碰到的難題。
全棧,讓設(shè)計(jì)更簡(jiǎn)單
前后端技術(shù)各有所長(zhǎng),有些事情用前端實(shí)現(xiàn)更簡(jiǎn)單,有些用后端實(shí)現(xiàn)更簡(jiǎn)單。綜合考量前端技術(shù)和后端技術(shù),往往可以產(chǎn)生更簡(jiǎn)單、更優(yōu)秀的設(shè)計(jì)。廣度在業(yè)務(wù)開發(fā)中往往比深度有用,這也是全棧工程師的優(yōu)勢(shì)所在。而團(tuán)隊(duì)中的技術(shù)專家主要負(fù)責(zé)深度。
分工是動(dòng)態(tài)的
技術(shù)專家或全棧工程師,并不是什么榮譽(yù)頭銜,只是分工不同而已。
同一個(gè)項(xiàng)目上你可以同時(shí)擔(dān)任全棧工程師和技術(shù)專家;這個(gè)項(xiàng)目你是全棧工程師,下一個(gè)項(xiàng)目上也可能專門擔(dān)任技術(shù)專家。團(tuán)隊(duì)的協(xié)作方式永遠(yuǎn)是動(dòng)態(tài)的、隨需應(yīng)變的。
不用擔(dān)心全棧會(huì)限制你的技術(shù)深度,實(shí)際上,全棧對(duì)提高你的技術(shù)深度是有幫助的,因?yàn)楹芏嗉夹g(shù)的“根”都是互通的。
相信你的直覺
資深后端首先是一個(gè)資深程序員,你對(duì)于“應(yīng)該如何”的期待,很可能是對(duì)的。如果你覺得 Angular 應(yīng)該有某項(xiàng)功能或某種設(shè)計(jì),它很可能確實(shí)有。去 Stackoverflow 搜一下,找找你的答案,這是你成為高級(jí) Angular 程序員的捷徑。
萬法歸一
形容某人聰明時(shí)經(jīng)常說“萬法皆通”,實(shí)際上“萬法皆通”不如“一法通而萬法通”。很多技術(shù)之間的相似程度超出你的想象,這些相似的部分其實(shí)就是技術(shù)的核心。用萬法歸一的思路去學(xué)習(xí)總結(jié),會(huì)給你帶來真正的提高。
資料 & 學(xué)習(xí)指南
學(xué)習(xí) Angular 的最佳資料是它的官方文檔,它無論是從準(zhǔn)確、全面,還是及時(shí)性等方面都是最佳的。
它的英文文檔站是 https://angular.io ,中文文檔站是 https://angular.cn ,這是由我和另外兩位社區(qū)志愿者共同翻譯的,期間還得到了很多社區(qū)志愿者的支持。中文文檔和英文文檔至少在每個(gè)大版本都會(huì)進(jìn)行一次同步翻譯。雖然時(shí)間有限導(dǎo)致語言上還有粗糙之處,不過你可以相信它的技術(shù)準(zhǔn)確度是沒問題的。
閱讀時(shí),請(qǐng)先閱讀架構(gòu)概覽 https://angular.cn/guide/architecture ,然后閱讀教程 https://angular.cn/tutorial (有經(jīng)驗(yàn)的程序員不需要跟著敲代碼,如果時(shí)間緊也可跳過),最后閱讀風(fēng)格指南 https://angular.cn/guide/styleguide 。風(fēng)格指南很重要,不用記住,但務(wù)必通讀一遍,有點(diǎn)印象供將來查閱即可。
文檔站中還提供了 API 參考手冊(cè),它提供了簡(jiǎn)單快速的站內(nèi)搜索功能,需要了解哪些細(xì)節(jié)時(shí)到里面查就可以了。
另外,ng-zorro 組件庫的一位開發(fā)者還整理了一份不完全指南,包括中英文資料: https://zhuanlan.zhihu.com/p/36385830 。