不吹不黑聊聊前端框架--尤雨溪Live整理
最近買了尤雨溪大大的Live:不吹不黑聊聊前端框架,這場Live讓我的前端思維到了前所未有的高度:當我們身為前端開發萌新,在前端人才金字塔的浮動與掙扎中思考該學什麼框架、該如何入門前端、又遇到學習瓶頸怎麼辦的時候,正是這些業界大牛們用自己的行動引導著我們,有如尤大所說:多思考場景需求,多看看技術到底做了怎樣的取捨,現在把相關的東西作為筆記整理下來,希望對前端開發有興趣的同學都可以去支援一下尤大
元件可以是函式
想象一下整個應用是一個大的函式,函式裡面可以呼叫別的函式,每一個元件是一個函式,一個元件可以呼叫其他的函式,整個一個樹狀結構
元件是有分類的
- 純展示型的元件,資料進,DOM出,直觀明瞭
- 接入型元件,在React場景下的container component,這種元件會跟資料層的service打交道,會包含一些跟伺服器或者說資料來源打交道的邏輯,container會把資料向下傳遞給展示型元件
- 互動型元件,典型的例子是對於表單元件的封裝和加強,大部分的元件庫都是以互動型元件為主,比如說Element UI,特點是有比較複雜的互動邏輯,但是是比較通用的邏輯,強調元件的複用
- 功能型元件,以Vue的應用場景舉例,路由的router-view元件、transition元件,本身並不渲染任何內容,是一個邏輯型的東西,作為一種擴充套件或者是抽象機制存在
JSX和模版的對比
JSX本質就是Javascript,它完全獲得了Javascript的靈活度,它最大的價值在於書寫功能型元件的時候比純模版更好,但是模版在展示型和其他的用例上也是不差的,模版會讓你更少的把邏輯放在視圖裡面,展示型的元件雖然在邏輯上比較簡單但是在樣式上還是具有一定的複雜度
程式碼的拆分(colocation)
把應該放一起的東西放一起,比如說在vue的單檔案元件裡面我們把模版、樣式和javascript的邏輯都是放在一起的,目前主流框架都是這麼做的,如果這概念擴充套件開來,元件的文件也是可以和元件的其他東西放一起的,元件化之後和傳統的Separation of concern就有區別了,傳統的Separation of concern是以語言為單位作切分,元件是以元件本身作為一個切分的抽象
變化偵測和渲染機制
- 渲染機制
現代的前端框架裡面渲染這塊最重要的是宣告式(Declarative),相比較之下的概念是命令式(Imperative),Imperative最直接的例子是我們使用Jquery時候拿到一個選擇器”直接幹”,使用命令去進行操作,直截了當,但是很快就會遇到維護性的問題,英文裡面有個詞叫做jQuery Spaghetti,Spaghetti的意思是義大利麵,這個詞的意思就是說你的程式碼寫到後面會像一坨義大利麵一樣,維護起來很困難,宣告式的好處就是說我們直接描述說資料和DOM結構之間的對映關係應該是怎麼樣的,不需要我們手動去做這些操作
React裡面有一個等式 view = render(state)
,這裡render就是我們在React裡面寫的render函式,vue的模版其實也是編譯成渲染函式的,所以模版和JSX之間的本質是相似的,輸入state,輸出DOM,理想情況下我們就是描述了這樣一種關係,那輸入變了輸出也會跟著變,我們不需要顧慮輸入和輸出之間發生了什麼事情,具體到底層實現可以是Virtual DOM,但並不一定得是Virtual DOM,可以是細粒度的繫結
- 變化偵測
用過vue的朋友知道vue的資料是響應式的,vue會把你傳遞的資料進行轉化,轉化過後當你改變一些屬性的值的時候,vue就會進行相應的更新,附上尤大相關的演講PPT,裡面有詳細的過程:
ppt
Live提問:
被人詬病,為啥 vue 的宣告式寫法就是推崇的?
問:一直有一個疑問,以前
答:HTML裡面這個onclick裡面的Javascript的作用域是全域性的,當你在vue裡面這麼寫的時候是有很明確的Javascript作用域的,你的繫結以及你的method所能觸及的影響範圍是設定好的,這個跟全域性的Javascript有本質的區別,另外當我們在vue或者Reac裡面這麼寫的時候你的Javascript邏輯和你的模版或者說你的JSX是在一起的,可以聯想我們之前提到的colocation的概念,所以並不會造成一個維護上的困難,但是如果你在全域性的Html裡面這樣直接裸寫,你完全不知道你這段Javascript可能會引用到哪裡的變數或者是呼叫的哪裡的方法
簡單的總結,變化偵測主要分為兩種:
- pull
所謂pull,系統不知道資料什麼時候變了,它需要一個訊號去告訴它說資料有可能變了,在這個系統才會去進行一次比較暴力的比對,在React裡面的表現是Virtual Dom Diff,在Angular裡面就是整個髒檢查的流程,能夠這麼做的前提是現在Javascript足夠快,雖然有浪費但是效能上也可以接受
- push
相比之下,vue的響應式資料或者RXJS的資料機制,在資料變動之後立刻就可以知道資料變動了,而且一定程式上我們會知道哪些資料變了,這樣就可以進行相對更細粒度的更新,pull的這種更新是最粗粒度的,所以在大型應用裡面我們要幫助系統來減少一些無用功,但是push的形式也有它的缺陷,粒度越細,你的每一個繫結都會需要一個observabel/watcher,這樣會帶來相應的記憶體以及依賴追蹤的開銷,所以在vue2裡面選擇的是一個比較中等粒度的方案,在元件級別是push,每一個元件是一個響應式的watcher,當資料變動時候我們可以對元件進行更新,在每個元件內部則是用Virtual Dom進行比對,push和pull之間的本質區別是在於用偵測成本換取一定程度的自動優化
狀態管理
狀態管理這個概念其實也是在FB提出了Flux之後才搬到檯面上來講,Flux在經歷了初期的混亂競爭之後慢慢的合流到了Redux上,vux在一定程度受到了Redux的影響,狀態管理的本質是從源事件(source event)對映到狀態的遷移和改變,然後在對映到UI的變化,宣告式的渲染已經幫我們解決了從狀態到UI的對映,這一塊,所以狀態管理這些庫他們做的實際上是如何管理將事件源對映到狀態變化的過程,如何將這個對映的過程從檢視元件中剝離出來,如何組織這一部分程式碼來提高可維護性,是狀態管理要解決的本質問題
現在的狀態管理方案還面臨一些其他的共同的尷尬,一個是元件的區域性狀態和全域性狀態如何區分,現在是區域性狀態和全域性狀態並沒有很明顯的區分,另一個是全域性狀態和服務端資料之間,現有的方案是把服務端抓過來的資料塞到store裡面去
路由
路由是隻有在大型的單頁應用才會遇到的一個問題,傳統的路由思想是比較有侵入式的,每個路由有自己的資料模型,有自己的模板等,但是當Reac和vue出現之後人們發現把路由和元件解耦是可行的並且還更加靈活,比如Reac直接用不帶路由是完全沒問題的,另一個啟示是,如果從元件出發去思考路由,本質上就變成了把一個url對映到元件樹結構的一個過程,url到元件的對映會有一些小的分歧,我們到底是應該從url出發,還是從這個狀態出發,其實本質是一樣的,因為url就是一個序列化的狀態。
當實際在SPA中去做一個你會發現路由會涉及到許多其他問題,比如說hash模式和history模式如何相容,重定向,別名,懶載入,然後最複雜的是跳轉,路由之間的跳轉需要提供各種”鉤子”,然後這些”鉤子”裡面又可能做非同步操作,”鉤子”裡面也有可能取消這次跳轉,使得這次跳轉無效等等。
整體來說現在主要的路由方案都有點相似,比較有意思的是最新的reat-router4,他推崇的是一種用元件本身來做路由的一種思路,這裡很大程度上利用了上述第四大元件”功能型元件”,在父元件裡面宣告式的渲染其他元件,跟傳統的路由元件方案的區別是”去中心化”,他不是把整個路由表寫在一個地方,是分散的寫在各個元件裡頭,這樣做的好處是靈活性非常好,但是也有一些問題,首先,集中式的路由表對於理解整個應用的結構是有幫助的,另一方面,去中心化的路由對於跳轉的管理會弱一些,他對於跳轉的管理是直接用元件的生命週期去做的。
web路由和app路由的區別:
目前web路由整體思路上是一樣的,將url對映到元件樹,從一個url跳轉到另一個url,我們把新的url push到歷史的stack裡面去了,但是stack前一個位置所對應的位置是被我們丟棄掉的,我們從一個狀態遷移到另一個狀態我們整個應用介面遷移到另一個狀態了,原生應用上的跳轉就像一疊卡片一樣,新的介面會蓋在現有的介面上,當你退回去的時候只是把當前的卡片拿掉,之前的卡片就會出現,web用的路由方案做app會比較彆扭。
CSS方案
主流的 CSS 方案
- 跟 JS 完全解耦,靠前處理器和比如 BEM 這樣的規範來保持可維護性,偏傳統
- CSS Modules,依然是 CSS,但是通過編譯來避免 CSS 類名的全域性衝突
- 各類 CSS-in-JS 方案,React 社群為代表,比較激進
- Vue 的單檔案元件 CSS,或是 Angular 的元件 CSS(寫在裝飾器裡面),一種比較折中的方案
比較 CSS 方案時首先要明確場景的問題,如果應用邏輯已經元件化了,是一個比較複雜應用的開發,傳統的 CSS 方式可維護性就有問題了
react-css-in-js
反對css-in-js的文章
傳統 css 的一些問題:
1. 作用域
2. Critical CSS
3. Atomic CSS
4. 分發複用
5. 跨平臺複用
css-in-js有很多不同的方案,這些方案各自解決了上述的一些問題,但是並不完美:
1. CSS Modules,Inline-Styles,vue的單檔案元件裡面直接加一個scoped都可以解決這個問題
2. 所謂的Critical ,比如說我們直出一個頁面,可能我們整個應用有幾十個頁面,但是我們直出的永遠是第一個頁面,如果沒一個頁面都有一個對應的CSS的話,理論上渲染首屏我們只需要首屏的CSS就夠了,這就是所謂的Critical CSS,在服務端渲染尤為重要,解決的辦法是在服務端渲染的時候偵測到渲染要用到哪些CSS,css-in-js和vue2.3+有一個執行時的功能,在編譯過程裡面可以把CSS的插入跟元件的生命週期掛鉤,同樣可以起到收集Critical CSS的效果
Live提問:
問:在vue裡 使用CSS Modules 會不會比 使用 scoped 好?
答:我個人覺得沒有什麼本質的區別,scoped的成本會更低一點,CSS Modules會有一定的執行時的代價,因為需要用動態的class繫結
- Atomic CSS的概念:比如說我們有兩條CSS規則,一條是color:red,一條是color:green,我們寫兩個button的樣式,一個按鈕是紅的,一個按鈕是綠的,原子類的話就會把color:red單獨拆成一個類:A,把color:green單獨拆成一個類:B,然後所有button共享的再拆成一個類:C,然後紅色的button可以說是AC,綠色的button是BC,總而言之就是把儘可能多的共享的一些單獨的規則都拆成一個很小很小的類,這樣出來的最後的結果是你的CSS可壓縮性更好了,可以變得更小,對應於css-in-js裡面的Style Chunk
- 分發複用的論點是說css-in-js都是Javascript,所以可以跟普通的Javascript模組一樣直接發包到npm上去複用,確實Javascript比純CSS更容易去組合複用,但是css你也可以發到npm上然後webpack直接引用,這一點上並不算完全的優勢
- 跨平臺複用:VueX裡面就是把靜態的css在parse之後編譯成Javascript,就可以跨平臺複用了
構建工具
構建工具解決的其實是幾方面的問題:
- 任務的自動化
- 開發體驗和效率(新的語言功能,語法糖,hot reload 等等)
- 部署相關的需求
- 編譯時優化
雖然 Vue 本身用 flow,但建議使用 TypeScript 的 flow,主要從開發體驗、生態完善度上考慮
服務端資料通訊
長久以來我們傳統的做法都是圍繞Rest,服務端如果暴露的是一個比較標準的Rest API,那我們客戶端就可以直接拿一個fetch直接去抓,或者圍繞Rest來做一個資源的抽象/封裝,特定的應用會遇到比較複雜的場景,一種是,資料直連,資料之間有大量的關聯性,另一類是有實時推送同步的需求,這種情況下傳統的Rest做法會比較痛苦。
Vue.js 伺服器端渲染指南
跨平臺渲染
從前端框架的角度去看,跨平臺渲染的本質是在設計框架的時候要讓框架的渲染機制和DOM解耦,這裡面有很多種實現方式,並不一定需要Virtual Dom,本質上只要把框架更新時候的一些節點操作封裝起來,你就可以做到跨平臺,一個原生的渲染引擎,比如 React Native 和 VueX本質都是在底層針對每個平臺有一個適配的渲染引擎,只要把渲染引擎暴露的結點操作的 API,跟框架執行時對接一下,就可以實現將框架裡面的程式碼渲染到原生的目的。這裡的解耦很清晰,這也是為什麼能看到 NG 可以接 React Native,VueX 可以跑 Vue 檔案,VueX 可以跑在 NativeScript 上等等。
新規範
- WebAssembly
是面向 Web 的通用二進位制和文字格式,可以跑在瀏覽器裡面。但是在目前的形勢下,WebAssembly 暫時還操作不了 DOM,對於框架的影響暫時比較有限,待觀望
總結
總結一下吧,我們聊了很多東西,可能比較雜,但我希望大家發現其中一些共性的東西:技術方案都是先有問題,再有思路,同時伴隨著取捨。在選擇衡量技術的時候,儘量去思考這個技術背後是在解決什麼問題,它做了怎樣的取捨。這樣一方面可以幫助我們更好的理解和使用這些技術,也為以後哪天你遇到業務中的特殊情況,需要自己做方案的時候打好基礎。