螞蟻前端研發(fā)最佳實(shí)踐
(給前端大學(xué)加星標(biāo),提升前端技能.)
作者:云謙
https://github.com/sorrycc/blog/issues/90
本文是阿里高級(jí)前端技術(shù)專家云謙在 2019.11.15 成都全棧大會(huì)分享的文字稿,介紹了螞蟻前端研發(fā)的最佳實(shí)踐,其中提取了三個(gè)比較重要的點(diǎn),每個(gè)點(diǎn)都是螞蟻實(shí)踐和深入思考后的結(jié)果,希望能對(duì)大家有所啟發(fā),歡迎探討。
開篇

準(zhǔn)備這個(gè)題目時(shí)我 google 了下前端最佳實(shí)踐,排在前面的是講前端代碼規(guī)范,語意、可讀性、編碼規(guī)范、空格還是 Tab 等等,我覺得這是我們第一代的最佳實(shí)踐。
而現(xiàn)在都 9012 年了,最佳實(shí)踐也經(jīng)歷了很多代的變更,下面是我們?cè)谶@方面的思考和實(shí)踐。
自我介紹

目錄

為什么要有最佳實(shí)踐?


不知大家在這些方面是否有疑惑?
前端發(fā)展快,每次發(fā)布 Umi 版本時(shí),除了點(diǎn)贊,還有人求別發(fā),表示實(shí)在學(xué)不動(dòng)了。右邊這張圖是之前的朋友圈看到的,轉(zhuǎn)到公司群里,有共鳴的人不少,說明一定程度反應(yīng)了現(xiàn)在前端社區(qū)的情況。
面對(duì)海量“輪子”,我應(yīng)該學(xué)哪些,不學(xué)哪個(gè)?,我的前端知識(shí)點(diǎn)學(xué)習(xí)列表已經(jīng)是完全學(xué)不完的狀態(tài),比如社區(qū)上光數(shù)據(jù)流方案就有幾百個(gè),其中值得一看的也有四五十個(gè)吧。
然后,大家在創(chuàng)建項(xiàng)目時(shí),是否也有過選擇困難?

這里舉一個(gè)具體的例子,
框架
基于 React 的框架
語言
CSS 方案
數(shù)據(jù)流
請(qǐng)求庫
等等
可以發(fā)現(xiàn),每個(gè)點(diǎn)都會(huì)有不少選擇,并且有時(shí)還真的很難選,因?yàn)槊總€(gè)人看待它的角度不同。所以,對(duì)于開發(fā)者來說,真的有點(diǎn)太難了!他只是想完成需求,然后回家睡覺,為啥需要選這么多,就不能給個(gè)默認(rèn)的嗎?

然后,
如果每個(gè)項(xiàng)目都要選,我覺得會(huì)很難。當(dāng)然,也有人可能會(huì)覺得是一種樂趣。
而對(duì)于團(tuán)隊(duì)而言,如果每個(gè)項(xiàng)目的選擇還都不一樣,那么團(tuán)隊(duì)的研發(fā)成本和效率都會(huì)是問題。想象一下,如果一個(gè)同學(xué)換個(gè)項(xiàng)目組,需要接觸完全不同的技術(shù)棧。
所以,對(duì)于團(tuán)隊(duì)而言,保持一致非常重要。

那么,如何保持一致?不同團(tuán)隊(duì)會(huì)有不同的選擇,通常有這幾類,
文檔
腳手架
框架
約束能力和迭代能力也是逐步遞增。
我們最早應(yīng)該是用的文檔。比如做一些代碼約定,用 Tab 還是空格,用兩個(gè)空格還是 4 個(gè)空格,行尾要不要加分號(hào)等等,這一類主要靠開發(fā)者自覺,所以我覺得不太靠譜,這也是為啥后來有 eslint。
腳手架比文檔好點(diǎn),但也依賴開發(fā)者的自覺性,因?yàn)檫€是可以隨便改。前幾年 React 社區(qū)上有不少做的很好的腳手架,但現(xiàn)在基本上都沒有活躍的了。
第三種方式是框架,他的約束性可以做的比較強(qiáng)。比如約定用 less,如果開發(fā)者用了 sass,就給他報(bào)個(gè)錯(cuò)。同時(shí)相比其他兩種方式,還有迭代能力。腳手架交給用戶之后是很難更新的,框架則是自己更新后,開發(fā)者的項(xiàng)目自動(dòng)生效。
當(dāng)然,這三者不是互斥關(guān)系,可以都用嘛。

然后如何決定用啥方案,用 SASS 還是 LESS,要不要用 TypeScript,甚至目錄用復(fù)數(shù)還是單數(shù)這種極其無聊的事情。
不同團(tuán)隊(duì)會(huì)有不同的選擇,
STAR 數(shù),大家通常會(huì)選 STAR 數(shù)多的,社區(qū)認(rèn)同感很重要,比如 DVA 在螞蟻的推廣就是先從社區(qū)做起的
簡單 vs. 規(guī)范,有人會(huì)選擇概念少而簡單的,有人會(huì)選擇概念多但看起來更規(guī)范的
先入為主,先占坑的往往具有優(yōu)勢(shì)
老板喜歡,?
老板喜歡其實(shí) “很重要”。有些大家吵很久但決定不了的事,往往會(huì)很自覺地找老板或者德高望重的同學(xué)進(jìn)行拍板,我們也是如此。
螞蟻前端的選擇


我們?cè)诓煌瑫r(shí)期的最佳實(shí)踐是不同的,曾經(jīng)還開發(fā)過 spm,不自量力地試圖挑戰(zhàn) npm + webpack 組合,雖然失敗了,但敢想也是一種勇氣。(做 spm 時(shí),webpack 還沒出來)

我們有很多方向,然后每個(gè)方向又有很多選擇,圖上是我們目前的選擇。
從這里可以看到幾點(diǎn),
選擇的內(nèi)容基本上是社區(qū)主流的,不脫離社區(qū)是基本原則
很多子方向都選擇了自研或者正在考慮自研
為啥要自研呢?

我覺得自研會(huì)帶來一些好處,
自主權(quán) vs. 成本,在擁有自主權(quán)的時(shí)候,需要評(píng)估其帶來的成本,以及潛在的棄坑可能
定制化,
需求滿足,社區(qū)方案有時(shí)并不能很好地契合我們的需求,尤其當(dāng)我們很深入地去使用的時(shí)候
售后服務(wù),出錯(cuò)是能找到 owner 的同學(xué)是非常重要的一點(diǎn)
有些開源庫看起來美好,但真正用下來會(huì)發(fā)現(xiàn)坑不少。比如組件的文檔工具,目前是選擇的 docz 和 storybook,但兩者用地都有些說不出來的不舒服,并且和 umi 是兩個(gè)生態(tài)的東西,所以我們正考慮基于 umi 開發(fā)自己的文檔工具,可能叫 umipress 或者 father-doc 。

沉淀的方式是以框架為主,文檔、腳手架、資產(chǎn)市場(chǎng)為輔。
框架具有更強(qiáng)大的約束性和迭代能力,這也是我們所需要的
對(duì)外是 Umi,面向社區(qū)
對(duì)內(nèi)是 Bigfish,在 Umi 的基礎(chǔ)上解決流程和業(yè)務(wù)問題
插件和插件集


我們把使用到的技術(shù)都沉淀到框架(Bigfish)里。框架像是一個(gè)魔法球,把各種技術(shù)棧吸到一起,加工后吐給用戶,以此來支撐業(yè)務(wù)。
對(duì)于用戶來說,Bigfish 框架是唯一依賴。唯一依賴會(huì)帶來一些實(shí)際的好處,這也是我們一直在內(nèi)部堅(jiān)持這一點(diǎn)的原因,
技術(shù)收斂,保持團(tuán)隊(duì)開發(fā)模式的一致性
無痛升級(jí),我們既要保持對(duì)社區(qū)的技術(shù)跟進(jìn),又要讓業(yè)務(wù)項(xiàng)目跟上步伐,這些中間的屎只能讓框架吃掉,讓開發(fā)者盡可能地?zé)o痛升級(jí)
應(yīng)用治理,相比散落的遍地開花的依賴,唯一依賴可以讓我們更好地推動(dòng)用戶升級(jí)框架,因?yàn)橹灰芤粋€(gè)點(diǎn)即可
唯一依賴的問題就是大而全,雖然看起來挺不優(yōu)雅,但實(shí)際用過之后會(huì)發(fā)現(xiàn)還蠻香的,除了一開始安裝他會(huì)有點(diǎn)慢。(這一點(diǎn)我們后續(xù)會(huì)通過啟動(dòng)器解決)

做了技術(shù)棧收斂之后,我覺得對(duì)外可能夠了,但對(duì)內(nèi)還遠(yuǎn)遠(yuǎn)不夠。
接流程,讓開發(fā)者能更順暢地跑通創(chuàng)建、本地開發(fā)、聯(lián)調(diào)、部署、發(fā)布和統(tǒng)計(jì)
接后端框架,后端可能是 Java、Node 或者 PHP(?),不同后端對(duì)于前端產(chǎn)物的要求會(huì)不同,在框架里做好對(duì)接,開發(fā)者就不用費(fèi)心思了
接場(chǎng)景,場(chǎng)景有很多種,在框架層也需做好對(duì)接。舉一些例子
SPA 應(yīng)該是目前用地最多的一種應(yīng)用類型,但有時(shí)也會(huì)不滿足需求
比如運(yùn)營頁面,多個(gè)頁面之間沒有一點(diǎn)點(diǎn)關(guān)系,也不需要互相跳轉(zhuǎn),用 SPA 就沒有意義,這時(shí)候 MPA 可能更適合
比如語雀,我們的文檔平臺(tái),他有前臺(tái)、有后臺(tái)、有 PC 端、有無線端,如果整體是一個(gè) SPA,不僅尺寸大,公共依賴的提取是個(gè)問題,不同場(chǎng)景之間可能還會(huì)相互影響,這時(shí)候,多 SPA 的組合會(huì)更適合他
微前端前面已經(jīng)提過
SSR 和 Prerender 則是為了更好的瀏覽器性能,順便解決 SEO 的問題
接服務(wù),比如登錄服務(wù),統(tǒng)計(jì)服務(wù),問卷服務(wù),評(píng)論服務(wù)等等
實(shí)現(xiàn)方式是一“件”接入,這里的件是插件,一個(gè)插件實(shí)現(xiàn)一個(gè)功能。然后,我們就有了很多插件。

有了插件之后,我們可以篩選一些插件出來形成插件集,以滿足某個(gè)業(yè)務(wù)的需求,類似 babel 的 plugin 和 preset,或者 eslint 的 rule 和 config。
**這種方式首先可以滿足不同業(yè)務(wù)的需求。**比如無線業(yè)務(wù),會(huì)比較關(guān)注性能,所以可能會(huì)選一個(gè)切 preact 到 react 的插件、極速版補(bǔ)丁插件、高清方案、fastclick 等等,形成一個(gè)插件集。
**然后還可以滿足一個(gè)技術(shù)的不同實(shí)現(xiàn),**在一個(gè)業(yè)務(wù)類型豐富的大團(tuán)隊(duì)中,是允許有不同的選擇的。比如數(shù)據(jù)流,大家的選擇可能不同,有些用 dva,有些用 hooks,有些用 mobx,有些自研一套;比如補(bǔ)丁方案,有常規(guī)版、極限版,還有終極版。

這是 umi 的插件三態(tài),講過好多次了,文字稿里就不重復(fù)了。

這是 umi 插件的示例。想提一點(diǎn)的是,會(huì)用 umi 和會(huì)寫 umi 插件是兩個(gè)完全不同的狀態(tài),會(huì)寫 umi 插件,你基本可以魔改 umi 內(nèi)部 70% 的功能,可以此來達(dá)到滿足需求業(yè)務(wù)需求的目的。


資產(chǎn)市場(chǎng)和場(chǎng)景市場(chǎng)


先來看下開發(fā)者的時(shí)間都去哪了。這是我咨詢了一些同事拍腦袋整理的,不太準(zhǔn)確。
20% 流程相關(guān),從創(chuàng)建到發(fā)布和發(fā)布后統(tǒng)計(jì)
40% 組件使用和開發(fā),如果有合適組件,直接使用;如果沒有,花時(shí)間開發(fā)
30% 交互場(chǎng)景,解決遇到某個(gè)交互場(chǎng)景如何處理,以及處理來自后臺(tái)的請(qǐng)求,把數(shù)據(jù)和視圖串起來
10% 其他
知道了時(shí)間分配后,大家應(yīng)該知道投時(shí)間去解決哪部分的問題,才能真正達(dá)到提效的目的了吧。

資產(chǎn)市場(chǎng)用于解決 40% 的開發(fā)者時(shí)間,非常重要。分為四個(gè)概念,
基礎(chǔ)組件,antd
業(yè)務(wù)組件,基于 antd 封裝的具有業(yè)務(wù)屬性的組件,不對(duì)外
區(qū)塊,組件的使用片段,區(qū)塊是為了方便地把代碼片段加到項(xiàng)目代碼中
模板,多個(gè)區(qū)塊組成的頁面

而資產(chǎn)市場(chǎng)要真正達(dá)到提效的目的,我覺得還需要解決一些關(guān)鍵的點(diǎn),才能讓整個(gè)流程跑起來。
資產(chǎn)質(zhì)量,組件參考 antd,區(qū)塊和模板是實(shí)實(shí)在在要被添加到用戶項(xiàng)目的代碼,我覺得比組件更難,需要形成對(duì)什么是好代碼的共同認(rèn)識(shí),誰都不希望自己的項(xiàng)目變臟
打通上下游,包括組件的生產(chǎn)和消費(fèi)。生產(chǎn)方是設(shè)計(jì)師和前端,需要保證組件的本地開發(fā)、文檔、打包、發(fā)布等環(huán)節(jié);消費(fèi)方也是設(shè)計(jì)師和前端,資產(chǎn)市場(chǎng)不僅是給前端用的,設(shè)計(jì)師也得用,只有前端拿到的設(shè)計(jì)稿有大量可以對(duì)應(yīng)的資產(chǎn)時(shí),前端開發(fā)才能真正提效,所以,設(shè)計(jì)師是否有能力讓資產(chǎn)市場(chǎng)覆蓋 50% 甚至 80% 的場(chǎng)景非常重要

這是內(nèi)部的資產(chǎn)市場(chǎng)和外部開源的 antd。


這是資產(chǎn)市場(chǎng)通過 umi ui 的方式使用,支持區(qū)塊、模板以及布局區(qū)塊。

右圖來自開源庫 Friend-List,這是一個(gè) suggestion 的實(shí)現(xiàn),他可以簡單做,也可以復(fù)雜做。復(fù)雜做的話,細(xì)節(jié)點(diǎn)就會(huì)很多,比如:
每次輸入都要做請(qǐng)求
快速輸入的時(shí)候,要使用最后的請(qǐng)求,并且取消前面的請(qǐng)求
輸入需要同步到 url
輸入還需要同步到 history,支持前進(jìn)后退
請(qǐng)求加緩存
請(qǐng)求出錯(cuò)處理
...
而如果每個(gè)開發(fā)者都要去關(guān)心這些細(xì)節(jié),會(huì)很難,成本也很高。那么如何讓開發(fā)者做到又快,產(chǎn)品體驗(yàn)又好,我覺得可能需要場(chǎng)景市場(chǎng),用于解決 30% 的交互場(chǎng)景需求。
沉淀方式可以用 hooks + 文檔的方式;覆蓋面從最簡單的 CURD 開始,到各種復(fù)雜場(chǎng)景。

這里是部分的場(chǎng)景舉例。
理想的工作流圖。
強(qiáng)約束的垂直領(lǐng)域框架

基于前面講的插件和插件集的方式,我們已經(jīng)能夠滿足各種豐富的業(yè)務(wù)場(chǎng)景,但是仍然給予了用戶很多選擇,選擇包括選擇插件,以及 umi 自身的大量配置項(xiàng)。
對(duì)于一些垂直領(lǐng)域,其實(shí)還可以做到更好,所以我們最近一直在思考“螞蟻前端應(yīng)該如何寫中臺(tái)代碼”。

有幾個(gè)關(guān)鍵的思路,
專治,不提供自由的技術(shù)棧選擇一定程度上會(huì)限制開發(fā)者的,但是效率高的,就看你要哪個(gè)了
極簡,不僅僅是簡單,還要優(yōu)雅;不僅要寫地少,還要寫地好
然后就強(qiáng)約束、配置化和約定化展開聊下。

前面我們已經(jīng)了解了一致性的重要性,所以何不把這一點(diǎn)做到底呢?
只能用 TypeScript,用 JavaScript 會(huì)報(bào)錯(cuò)
只能用 less + css modules,用 sass、stylus、css in js 會(huì)報(bào)錯(cuò)
只能用內(nèi)置的數(shù)據(jù)流方案,用 redux、mobx 等等會(huì)報(bào)錯(cuò)
等等
圖上只列了一部分。
這里的有些約束甚至?xí)行┓慈祟?,但我覺得約束越強(qiáng),越能保持大家的一致性,如果我們已經(jīng)把這條路探地很清楚了,少給選擇或許是更好的選擇。有些限制還不確定是不是好的方式,但是第一版會(huì)盡量把規(guī)則收攏地緊一些。

配置化不僅是框架和插件的配置,還包括 UI 。
右圖是 ant-design-pro 的圖,其中 LOGO、導(dǎo)航、菜單對(duì)于 90% 的每個(gè)頁面來說都是固定的,變化的只有右下的頁面區(qū),所以我們何不把固定的部分做成配置呢?
比如:
export?default?{
??layout:?{
????logo:?string;
????title:?string;
????renderRender:?function;
????logout:?function;
??},
??routes:?[
????{
??????path,
??????//?菜單配置
??????menu:?{?name,?icon,?showBreadcrumb?},
??????//?權(quán)限配置
??????access,
????},
??],
}Layout 是其中一個(gè)例子,還可以有更多 UI 的配置化。這也是在一定程度在像 low code 的模式靠,我覺得某些研究地很透的垂直場(chǎng)景下,low code 能讓研發(fā)更高效。
所以我們把適合做成配置的全部配置化,而不能配置的,則會(huì)走約定化。

之前有用過 ruby on rails 框架,特別喜歡那種約定化的編碼方式,所以我們希望把他也搬到前端研發(fā)流程里。
建一個(gè) locales 目錄,就擁有了國際化
建一個(gè) models 目錄,就擁有了數(shù)據(jù)流
建一個(gè) mock 目錄,就擁有了數(shù)據(jù) mock
建一個(gè) access.ts 文件,就擁有了權(quán)限策略
...
看起來很黑盒,按照我們約定的方式編碼,并且只能這樣編碼,然后他就能 run 起來。

這是之前在朋友圈看到的圖,大家體會(huì)下,但這就是我們想要實(shí)現(xiàn)的樣子。

極簡數(shù)據(jù)流是整體方案的其中一環(huán)。
右邊是之前做數(shù)據(jù)流調(diào)研時(shí)做的整理,發(fā)現(xiàn)那么多數(shù)據(jù)流方案基本都是在這些方案上的差異,而要選哪個(gè)就看你對(duì)哪些方面比較關(guān)心。這部分展開聊比較長,之后會(huì)額外寫一篇文章介紹。
然后我們還調(diào)研了下公司內(nèi)部的中臺(tái)項(xiàng)目,發(fā)現(xiàn)大部分是簡單的 CURD,并且全局?jǐn)?shù)據(jù)使用較少,比如通知、登錄、當(dāng)前用戶信息等。所以,我們可能是需要一個(gè)不那么復(fù)雜的,用起來又很簡單的數(shù)據(jù)流方案。

最終討論下來的方案有幾個(gè)特點(diǎn),
基于 hooks,在看到?swr?之后,我開始有點(diǎn)覺得在數(shù)據(jù)流里用 hooks 可能是未來的趨勢(shì),因?yàn)榇罅康慕换?chǎng)景都可以通過 hook 沉淀,但也有一點(diǎn)點(diǎn)擔(dān)心
和框架強(qiáng)綁,脫離框架我們可能沒有優(yōu)勢(shì),但是有框架加持,就能做到比社區(qū)大量的 hooks 數(shù)據(jù)流都“好用”,因?yàn)橹虚g復(fù)雜的事情可以交給框架處理,比如手寫 Provider,比如自動(dòng)處理 model 依賴等等
約定式 model 定義,在 models 目錄下建文件導(dǎo)出 hooks 就是一個(gè) model
單一 API,在組件層或者 model 層通過?
useModel?來使用
總結(jié)

文字就不復(fù)述了。
這里和大家分享了螞蟻前端研發(fā)實(shí)踐中三個(gè)重要的點(diǎn),但其實(shí)還有更多的點(diǎn),比如說 UMI UI,如果感興趣,可以來聽我在 12 月 GMTC 深圳的演講。
分享前端好文,點(diǎn)個(gè)?在看?
