午夜视频在线观看你懂的-国产对白videos高潮内射-成人国产一区二区三区av-亚洲欧美中文日本在线视频

前端教程
當(dāng)前位置: 主頁 > 資訊 > 前端教程
從自研走向開源的 TinyVue 組件庫
發(fā)布日期:2023-07-13 閱讀次數(shù):

  OpenTiny 提供企業(yè)級的 Web 應(yīng)用前端開發(fā)套件,包括 TinyVue/TinyNG 組件庫、TinyPro 管理系統(tǒng)模板、TinyCLI 命令行工具以及 TinyTheme 主題配置系統(tǒng)等。這些前端開發(fā)的基礎(chǔ)設(shè)施和技術(shù)已在華為內(nèi)部積累和沉淀多年,其中 TinyVue 組件庫更是歷經(jīng)九年的磨練,從最初的封閉自研逐步走向社區(qū)開源。

  TinyVue 九年的開源征程大致分為三個階段:第一階段走完全自研的線路,當(dāng)時稱為 HAE 前端框架;第二階段開始引入開源的 Vue 框架,更名為 AUI 組件庫;第三階段對架構(gòu)進(jìn)行重新設(shè)計,并逐步演變?yōu)楝F(xiàn)在開源的 TinyVue 組件庫。本文將圍繞 TinyVue 三個階段的技術(shù)發(fā)展歷程,深入代碼細(xì)節(jié)講解不同階段的核心競爭力。

  時間回到2014年,彼時的我剛加入華為公共技術(shù)平臺部,參與 HAE 前端框架的研發(fā)。HAE 的全稱是 Huawei Application Engine,即華為應(yīng)用引擎。當(dāng)時我們部門負(fù)責(zé)集團(tuán) IT 系統(tǒng)的基礎(chǔ)設(shè)施建設(shè),在規(guī)劃 HAE 時我們對行業(yè)和技術(shù)趨勢進(jìn)行了分析,并得出結(jié)論:云計算、大數(shù)據(jù)牽引 IT 架構(gòu)變化,并帶來商業(yè)模式轉(zhuǎn)變和產(chǎn)品變革,而云計算和大數(shù)據(jù)需要新的 IT 基礎(chǔ)架構(gòu)的支撐。

  基于這個背景,我們提出 IT 2.0 架構(gòu)的目標(biāo):利用互聯(lián)網(wǎng)技術(shù)打造面向未來的更高效、敏捷的下一代 IT。作為云開發(fā)平臺,HAE 需要支持全面的云化:云端開發(fā)、云端測試、云端部署、云端運營,以及應(yīng)用實施的云化。其中,云端開發(fā)由 Web IDE 負(fù)責(zé)實現(xiàn),這個 IDE 為用戶提供基于配置的前端開發(fā)能力,因此需要支持可配置的 HAE 前端框架。

  基于配置的開發(fā)模式,用戶可通過可視化界面來配置前端應(yīng)用開發(fā)中的各種選項,比如定義系統(tǒng)生命周期、配置頁面路由、設(shè)置組件的屬性等。相比之下,傳統(tǒng)的開發(fā)模式需要用戶手寫代碼來實現(xiàn)這些功能。當(dāng)時業(yè)界還沒有能滿足這種需求的前端開發(fā)框架,走完全自研的路是歷史必然的選擇。

  在2014年,主流的前端技術(shù)仍以 jQuery 為主。傳統(tǒng)的 jQuery 開發(fā)方式是通過手動操作 DOM 元素來更新和響應(yīng)數(shù)據(jù)的變化。開發(fā)者需要編寫大量的代碼來處理數(shù)據(jù)和 DOM 之間的同步。這種單向的數(shù)據(jù)流和手動操作的方式,在處理復(fù)雜應(yīng)用的數(shù)據(jù)和視圖之間的同步時,可能會導(dǎo)致代碼冗余、維護(hù)困難以及出錯的可能性增加。

  當(dāng)時剛剛興起的 AngularJS 帶來了數(shù)據(jù)雙向綁定的概念。數(shù)據(jù)雙向綁定可以自動將數(shù)據(jù)模型和視圖保持同步,當(dāng)數(shù)據(jù)發(fā)生變化時,視圖會自動更新,反之亦然。這種機制減少了開發(fā)者在手動處理數(shù)據(jù)和 DOM 同步方面的工作量。通過AngularJS 開發(fā)者只需要關(guān)注數(shù)據(jù)的變化,而不必顯式地更新 DOM 元素。這使得開發(fā)過程更加簡潔、高效,并減少了出錯的可能性。

  在 HAE 前端框架的研發(fā)初期,為了引入數(shù)據(jù)雙向綁定功能,我們在原本基于 jQuery 的架構(gòu)上融合了 AngularJS 框架。然而,經(jīng)過幾個月的嘗試,我們發(fā)現(xiàn)這種融合方式存在兩個嚴(yán)重問題:首先,當(dāng)頁面綁定數(shù)據(jù)量較大時,性能顯著下降。其次,框架同時運行兩套生命周期,導(dǎo)致兩者難以協(xié)調(diào)相互同步。因此,我們決定移除 AngularJS 框架,但又不想放棄已經(jīng)使用的數(shù)據(jù)雙向綁定的特性。

  在此情形下,我們深入研究 AngularJS 的數(shù)據(jù)雙向綁定。它采用臟讀(Dirty Checking)機制,該機制通過定期檢查數(shù)據(jù)模型的變化來保持視圖與模型之間的同步。當(dāng)數(shù)據(jù)發(fā)生變化時,AngularJS 會遍歷整個作用域(Scope)樹來檢查是否有任何綁定的值發(fā)生了改變。當(dāng)綁定的數(shù)據(jù)量較大時,這個過程會消耗大量的計算資源和時間。

  用什么方案替換 AngularJS 的數(shù)據(jù)雙向綁定,并且還要保證性能優(yōu)于臟讀機制?當(dāng)時我們把目光投向 ES5 的 Object.defineProperty 函數(shù),借助它的 getter 和 setter 方法實現(xiàn)了數(shù)據(jù)雙向綁定。這個方案比 Vue 框架早了整整一年,直到2015年10月,Vue 1.0 才正式發(fā)布。

  接下來,我們通過代碼來展現(xiàn)該方案的技術(shù)細(xì)節(jié),以下是 AngularJS 數(shù)據(jù)雙向綁定的使用示例:

  我們的替換方案就是要實現(xiàn)上面示例中的 $scope 變量,該變量擁有一個可以雙向綁定的 message 屬性。從以下 HTML 代碼片段:

  可以得知 input 輸入框的值綁定了 message 屬性,而另一段代碼:

  要實現(xiàn) $scope 變量,就必須實現(xiàn)一個 Scope 類,$scope 變量就是 Scope 類的一個實例。這個類有一個添加屬性的方法,假設(shè)方法名為 $addAttribute,并且還有一個監(jiān)聽屬性的方法,假設(shè)方法名為 $watch。

  上述代碼因篇幅關(guān)系并不包含數(shù)據(jù)綁定的功能,僅實現(xiàn)為 $scope 添加屬性以及設(shè)置監(jiān)聽回調(diào)函數(shù),并且考慮了多個監(jiān)聽回調(diào)函數(shù)的執(zhí)行順序及異常處理的情況。借助 Object.defineProperty,我們不再需要遍歷整個作用域(Scope)樹來檢查是否有任何綁定的值發(fā)生了改變。一旦某個值發(fā)生變化,就會立即觸發(fā)綁定的監(jiān)聽回調(diào)函數(shù),從而解決臟讀機制的性能問題。

  同樣在2014年,具有面向?qū)ο缶幊烫匦缘?TypeScript 仍處于早期階段,微軟在當(dāng)年4月份發(fā)布了 TypeScript 1.0 版本。HAE 前端框架在研發(fā)初期沒有選擇不成熟的 TypeScript 方案,而是利用 JavaScript 原型鏈機制來實現(xiàn)面向?qū)ο蟮脑O(shè)計模式,即通過共享原型對象的屬性和方法,實現(xiàn)對象之間的繼承和多態(tài)性。

  然而,使用 JavaScript 原型鏈來實現(xiàn)面向?qū)ο笤O(shè)計存在兩個問題:首先,原型鏈的使用方式與傳統(tǒng)的面向?qū)ο缶幊陶Z言(例如 Java 和 C++)有明顯的區(qū)別,在當(dāng)時前端開發(fā)人員大多是由 Java 后端轉(zhuǎn)行做前端,因此需要花費較高的學(xué)習(xí)成本來適應(yīng)原型鏈的概念和用法。其次,JavaScript 本身沒有提供顯式的私有屬性和方法的支持,我們一般通過在屬性或方法前添加下劃線等約定性命名,來暗示這是一個私有成員。而在實際開發(fā)過程中,用戶往往會直接訪問和使用這些私有成員,這導(dǎo)致在后續(xù)框架的升級過程中必須考慮向下兼容這些私有成員,從而增加了框架的開發(fā)成本。

  為了解決上述問題,我們自研了 jClass 庫,這個庫用 JavaScript 模擬實現(xiàn)了面向?qū)ο缶幊陶Z言的基本特性。jClass 不僅支持真正意義上的私有成員,還支持保護(hù)成員、多重繼承、方法重載、特性混入、靜態(tài)常量、類工廠和類事件等,此外還內(nèi)置自研的 Promise 異步執(zhí)行對象,提供動態(tài)加載類外部依賴模塊的功能等。

  接下來,我們通過代碼來展現(xiàn) jClass 面向?qū)ο蟮奶匦?,以下是基本使用示例?

  我們再來看 jClass 如何繼承多個父類,在上述代碼后面添加如下代碼:

  jClass 的類工廠與類繼承有相似之處,而類事件則為類方法的調(diào)用、類屬性的修改提供監(jiān)聽能力,兩者的使用示例如下:

  基于 jClass 面向?qū)ο蟮奶匦?,我們就可以用面向?qū)ο蟮脑O(shè)計模式來開發(fā) HAE 組件庫,以下就是定義和擴展組件的示例:

  上述代碼只演示了 jClass 部分特性,因篇幅關(guān)系沒有展示其實現(xiàn)的細(xì)節(jié)。從2014年10月開始,jClass 陸續(xù)支撐 120 多個組件的研發(fā),累積 30 多萬行的代碼。經(jīng)過四年的發(fā)展,作為 HAE 前端框架的基石,jClass 在華為內(nèi)部 IT 各個領(lǐng)域 1000 多個項目中得到廣泛應(yīng)用。通過這些項目的不斷磨練,jClass 在功能和性能上已經(jīng)達(dá)到了企業(yè)級的要求。

  一個前端框架需要支撐生命周期,主要目的是在 Web 應(yīng)用的不同階段提供可控的執(zhí)行環(huán)境和鉤子函數(shù),以便開發(fā)者可以在適當(dāng)?shù)臅r機執(zhí)行特定的邏輯和操作。通過生命周期的支持,前端框架能夠更好地管理 Web 應(yīng)用的初始化、渲染、更新和銷毀等過程,提供更靈活的控制和擴展能力。

  在 HAE 前端框架中,存在三個不同層次的生命周期:系統(tǒng)生命周期、頁面生命周期和組件生命周期。

  系統(tǒng)生命周期:系統(tǒng)生命周期指的是整個前端應(yīng)用的生命周期,它包含了應(yīng)用的啟動、初始化、運行和關(guān)閉等階段。系統(tǒng)生命周期提供了應(yīng)用級別的鉤子函數(shù),例如應(yīng)用初始化前后的鉤子、應(yīng)用銷毀前后的鉤子等。通過系統(tǒng)生命周期的支持,開發(fā)者可以在應(yīng)用級別執(zhí)行一些全局的操作,例如加載配置、注冊插件、處理全局錯誤等。

  頁面生命周期:頁面生命周期指的是單個頁面的生命周期,它描述了頁面從加載到卸載的整個過程。頁面生命周期包含了頁面的創(chuàng)建、渲染、更新和銷毀等階段。在頁面生命周期中,HAE 前端框架提供了一系列鉤子函數(shù),例如頁面加載前后的鉤子、頁面渲染前后的鉤子、頁面更新前后的鉤子等。通過頁面生命周期的支持,開發(fā)者可以在頁面級別執(zhí)行一些與頁面相關(guān)的邏輯,例如獲取數(shù)據(jù)、處理路由、初始化頁面狀態(tài)等。

  組件生命周期:組件生命周期指的是單個組件的生命周期,它描述了組件從創(chuàng)建到銷毀的整個過程。組件生命周期包含了組件的實例化、掛載到 DOM、更新和卸載等階段。組件生命周期的鉤子函數(shù)與頁面生命周期類似,通過組件生命周期的支持,開發(fā)者可以在組件級別執(zhí)行一些與組件相關(guān)的邏輯,例如初始化狀態(tài)、處理用戶交互、與外部組件通信等。

  總的來說,系統(tǒng)生命周期、頁面生命周期和組件生命周期在粒度和范圍上有所不同。系統(tǒng)生命周期操作整個 Web 應(yīng)用,頁面生命周期操作單個頁面,而組件生命周期操作單個組件。通過這些不同層次的生命周期,HAE 前端框架能夠提供更精細(xì)和靈活的控制,使開發(fā)者能夠在合適的時機執(zhí)行相關(guān)操作,實現(xiàn)更高效、可靠和可擴展的前端應(yīng)用。

  基于配置的開發(fā)模式,HAE 前端框架要讓用戶通過配置的方式,而不是通過手寫代碼來定義生命周期的鉤子函數(shù)。為此,我們引入 Windows 注冊表的概念,將框架內(nèi)置的默認(rèn)配置信息保存在一個 JSON 對象中,并命名為 register.js。同時,每個應(yīng)用也可以根據(jù)自身需求創(chuàng)建應(yīng)用的 register.js,系統(tǒng)在啟動前會合并這兩個文件,從而按照用戶期望的方式配置生命周期的鉤子函數(shù)。

  生命周期的鉤子函數(shù)在哪里體現(xiàn)?其實答案就在服務(wù)里面,以 Hae_Service_Mock 服務(wù)為例,下面是該服務(wù)的定義:

  以上代碼需要包含在 load_modules 階段加載的模塊中。除了調(diào)用 loadService 方法定義服務(wù)之外,還可以通過 jClass 定義類的方式定義服務(wù),代碼示例如下:

  上面的 Hae 變量就是加強版的 jClass。我們再來看一下框架內(nèi)置的注冊表中,有關(guān)頁面生命周期的定義:

  系統(tǒng)生命周期各個服務(wù)多以開關(guān)的形式定義,而頁面生命周期各個服務(wù)多以名稱的形式定義,以 Hae.Service.DataBind 服務(wù)為例,其定義如下:

  最后再簡單介紹一下組件的生命周期,借助 jClass 面向?qū)ο蟮奶匦裕M件的生命周期各階段鉤子函數(shù)在組件的基類 Widget 中定義的,代碼示例如下:

  可以看到組件基類的編譯模板階段和渲染模板階段都有默認(rèn)的實現(xiàn),由于這兩個階段一般需要讀取后端數(shù)據(jù)等延遲操作,因此要返回 Hae.Promise 異步對象。這個異步對象是 HAE 框架參照 jQuery 的 Deferred 異步回調(diào)重新實現(xiàn)的,主要解決 Deferred 異步性能慢的問題。

  時間來到2017年,以 Vue 為代表的現(xiàn)代前端工程化開發(fā)模式帶來了許多改進(jìn)和變革。與以 jQuery 為代表的傳統(tǒng)開發(fā)模式相比,這些改進(jìn)和變革體現(xiàn)在以下方面:

  聲明式編程:Vue 采用了聲明式編程的思想,開發(fā)者可以通過聲明式的模板語法編寫組件的結(jié)構(gòu)和行為,而無需直接操作 DOM。這簡化了開發(fā)流程并提高了開發(fā)效率。

  組件化開發(fā):Vue 鼓勵組件化開發(fā),將 UI 拆分為獨立的組件,每個組件具有自己的狀態(tài)和行為。這樣可以實現(xiàn)組件的復(fù)用性、可維護(hù)性和擴展性,提高了代碼的可讀性和可維護(hù)性。

  響應(yīng)式數(shù)據(jù)綁定:Vue 采用了響應(yīng)式數(shù)據(jù)綁定的機制,將數(shù)據(jù)與視圖自動保持同步。當(dāng)數(shù)據(jù)發(fā)生變化時,自動更新相關(guān)的視圖部分,大大簡化了狀態(tài)管理的復(fù)雜性。

  自動化流程:前端工程化引入了自動化工具,例如構(gòu)建工具(例如 Webpack)、任務(wù)運行器(例如 npm)和自動化測試工具,大大簡化了開發(fā)過程中的重復(fù)性任務(wù)和手動操作。通過自動化流程,開發(fā)者可以自動編譯、打包、壓縮和優(yōu)化代碼,自動執(zhí)行測試和部署等,提高了開發(fā)效率和一致性。

  模塊化開發(fā):前端工程化鼓勵使用模塊化開發(fā)的方式,將代碼拆分為獨立的模塊,每個模塊負(fù)責(zé)特定的功能。這樣可以提高代碼的可維護(hù)性和復(fù)用性,減少了代碼之間的耦合性,使團(tuán)隊協(xié)作更加高效。

  規(guī)范化與標(biāo)準(zhǔn)化:前端工程化倡導(dǎo)遵循一系列的規(guī)范和標(biāo)準(zhǔn),包括代碼風(fēng)格、目錄結(jié)構(gòu)、命名約定等。這樣可以提高團(tuán)隊協(xié)作的一致性,減少溝通和集成的成本,提高項目的可讀性和可維護(hù)性。

  靜態(tài)類型檢查和測試:前端工程化鼓勵使用靜態(tài)類型檢查工具(例如 TypeScript)和自動化測試工具(例如 Mocha)來提高代碼質(zhì)量和穩(wěn)定性。通過靜態(tài)類型檢查和自動化測試,可以提前捕獲潛在的錯誤和問題,減少Bug的產(chǎn)生和排查的時間。

  考慮到人力成本、學(xué)習(xí)曲線和競爭力等因素,HAE 前端框架需要向現(xiàn)代前端開源框架與工程化方向演進(jìn)。由于 HAE 屬于自研框架,僅在華為內(nèi)部使用,新進(jìn)的開發(fā)人員需要投入時間學(xué)習(xí)和掌握該框架,這對他們的技術(shù)能力要求較高。然而,如果選擇采用開源框架,龐大的社區(qū)支持和廣泛的文檔資源,使得開發(fā)人員可以更快速地上手和開發(fā)。同時,采用開源框架也使得 HAE 框架能夠緊跟業(yè)界趨勢。開源框架通常由全球的開發(fā)者社區(qū)共同維護(hù)和更新,能夠及時跟進(jìn)最新的前端技術(shù)和最佳實踐。這有助于提升 HAE 框架自身的競爭力,使其具備更好的適應(yīng)性和可擴展性。

  早在2016年10月上海 QCon 大會上,Vue 框架的作者尤雨溪首次亮相,登臺推廣他的開源框架,那也是我們初次接觸 Vue。當(dāng)時 React 作為另一個主流的開源框架也備受業(yè)界關(guān)注,我們需要在 Vue 和 React 之間做出選擇。隨后,在2017年6月我們遠(yuǎn)赴波蘭的佛羅茨瓦夫參加 Vue 首屆全球開發(fā)者大會,那次我們有幸與尤雨溪本人進(jìn)行了交流?;貋砗?,我們提交了 Vue 與 React 的對比分析報告,向上級匯報了我們的技術(shù)選型意向,最終我們決定選擇 Vue。

  要全面遷移到 Vue 框架,拋棄已使用 jQuery 開發(fā)的 30 萬行代碼,在有限的時間和人力下是一個巨大的挑戰(zhàn)。為了找到折中的解決方案,我們采取這樣的遷移策略:將 HAE 前端框架的系統(tǒng)和頁面生命周期進(jìn)行剝離,只保留與 HAE 組件相關(guān)的代碼,然后將底層架構(gòu)替換為 Vue,并引入所有前端工程化相關(guān)的能力,最后成功實現(xiàn)了讓用戶以 Vue 的方式來使用我們的組件。這樣的遷移策略在保證項目進(jìn)展的同時,也能夠逐步融入 Vue 的優(yōu)勢和工程化的便利性。

  上述示例代碼定義了一個名為 my-slider 的 Vue 組件,在該組件生命周期的 mounted 階段,通過調(diào)用 webix.ui 方法動態(tài)創(chuàng)建了一個 Webix 組件,然后監(jiān)聽該組件的 onChange 事件并拋出 Vue 的 update:modelValue 事件,并且利用 Vue 的 watch 監(jiān)聽其 value 屬性,一旦它發(fā)生變化則調(diào)用 Webix 的 setValue 方法重新設(shè)置 Webix 組件的值,從而實現(xiàn)數(shù)據(jù)的雙向綁定。由于 HAE 組件也支持動態(tài)創(chuàng)建,按照這個思路,我們很快寫出 HAE 版本的 Vue 組件:

  以上的遷移策略畢竟是折中的臨時方案,并沒有充分發(fā)揮 Vue 的模板和虛擬 DOM 優(yōu)勢,相當(dāng)于用 Vue 套了一層殼。雖然該方案也提供了數(shù)據(jù)雙向綁定功能,但不會綁定數(shù)組的每個元素,并不是真正的基于數(shù)據(jù)驅(qū)動的組件。這個臨時方案的好處在于為我們贏得了時間,以最快速度引入開源的 Vue 框架以及前端工程化的理念,使得業(yè)務(wù)開發(fā)能夠盡早受益于前端變革所帶來的降本增效。

  經(jīng)過近半年的研發(fā),HAE 組件庫成功遷移到 Vue 框架,并于2017年12月正式發(fā)布。在2018年,為統(tǒng)一用戶體驗遵循 Aurora 主題規(guī)范,我們對組件庫進(jìn)行升級改造,并改名為 AUI。在支撐了制造、采購、供應(yīng)、財經(jīng)等領(lǐng)域的大型項目后,到了2019年 AUI 進(jìn)入成熟穩(wěn)定期,我們才有時間去思考如何將 jQuery 的 30 萬行代碼重構(gòu)為 Vue 的代碼。

  在 HAE 框架中,組件能夠自動連接后端服務(wù)以獲取數(shù)據(jù),無需開發(fā)人員編寫請求代碼或處理返回數(shù)據(jù)的格式。例如人員聯(lián)想框組件,其功能是根據(jù)輸入的工號返回相應(yīng)的姓名。該組件已經(jīng)預(yù)先定義了與后端服務(wù)進(jìn)行通信所需的接口和數(shù)據(jù)格式,因此開發(fā)人員只需要在頁面中引入該組件即可直接使用。

  這樣的設(shè)計使得開發(fā)人員能夠更專注于頁面的搭建和功能的實現(xiàn),而無需關(guān)注與后端服務(wù)的具體通信細(xì)節(jié)。HAE 框架會自動處理請求和響應(yīng),并確保數(shù)據(jù)以一致的格式返回給開發(fā)人員。通過這種自動連接后端服務(wù)的方式,開發(fā)人員能夠節(jié)省大量編寫請求代碼和數(shù)據(jù)處理邏輯的時間,加快開發(fā)速度,同時減少了潛在的錯誤和重復(fù)勞動。

  HAE 框架的后端服務(wù)是配套的,組件設(shè)計當(dāng)初沒有考慮連接不同的后端服務(wù)。升級到 AUI 組件庫之后,業(yè)務(wù)的多樣化場景使得我們必須引入適配器來實現(xiàn)對接不同后端服務(wù)的需求。適配器作為一個中間層,其目的和作用如下:

  解耦前后端:適配器充當(dāng)前后端之間的中間層,將前端組件與后端服務(wù)解耦。通過適配器,前端組件不需要直接了解或依賴于后端服務(wù)的具體接口和數(shù)據(jù)格式。這種解耦使得前端和后端能夠獨立地進(jìn)行開發(fā)和演進(jìn),而不會相互影響。

  統(tǒng)一接口:不同的后端服務(wù)可能具有不同的接口和數(shù)據(jù)格式,這給前端組件的開發(fā)帶來了困難。適配器的作用是將不同后端服務(wù)的接口和數(shù)據(jù)格式轉(zhuǎn)化為統(tǒng)一的接口和數(shù)據(jù)格式,使得前端組件可以一致地與適配器進(jìn)行交互,而不需要關(guān)心底層后端服務(wù)的差異。

  靈活性和擴展性:通過適配器,前端組件可以輕松地切換和擴展后端服務(wù)。如果需要替換后端服務(wù)或新增其他后端服務(wù),只需添加或修改適配器,而不需要修改前端組件的代碼。這種靈活性和擴展性使得系統(tǒng)能夠適應(yīng)不同的后端服務(wù)需求和變化。

  隱藏復(fù)雜性:適配器可以處理后端服務(wù)的復(fù)雜性和特殊情況,將這些復(fù)雜性隱藏在適配器內(nèi)部。前端組件只需與適配器進(jìn)行交互,無需關(guān)注后端服務(wù)的復(fù)雜邏輯和細(xì)節(jié)。這種抽象和封裝使得前端組件的開發(fā)更加簡潔和高效。

  以 AUI 內(nèi)置的 Jalor 和 HAE 兩個后端服務(wù)適配器為例,對于相同的業(yè)務(wù)服務(wù),我們來看一下這兩個后端服務(wù)接口的差異,以下是 Jalor 部分接口的訪問地址:

  這些相同的業(yè)務(wù)服務(wù)不僅接口訪問地址不同,就連請求的參數(shù)格式以及返回的數(shù)據(jù)格式都有差異。適配器就是為開發(fā)人員提供統(tǒng)一的 API 來連接這些有差異的服務(wù)。在具體實現(xiàn)上,我們首先創(chuàng)建一個核心層接口 @aurora/core,以下是該接口的示例代碼:

  然后我們?yōu)槊恳粋€后端服務(wù)創(chuàng)建一個適配器,以下是 Jalor 適配器 @aurora/service-jalor 的示例代碼:

  以上面的 fetchArea 方法為例,Jalor 服務(wù)的實現(xiàn)代碼如下:

  兩者主要區(qū)別在于 parmas 參數(shù)以及 response.data 數(shù)據(jù)格式。有了統(tǒng)一的 API 接口,開發(fā)人員只需按以下方式調(diào)用 getArea 方法就能獲取地區(qū)的數(shù)據(jù),不需要區(qū)分?jǐn)?shù)據(jù)來自是 Jalor 服務(wù)還是 HAE 服務(wù):

  AUI 組件繼承了 HAE 框架的特點,即天然支持配置式開發(fā)。如何理解這個配置式開發(fā)?我們用前面 封裝成 Vue 組件 章節(jié)里的代碼來講解:

  代碼中的 op 變量是 option 配置項的縮寫,變量的值為一個 JSON 對象,該對象描述了創(chuàng)建 HAE 的 Slider 組件所需的配置信息。這些配置信息在 HAE 框架中通過 Web IDE 的可視化設(shè)置面板來收集,這就是配置式開發(fā)的由來。相比之下,如果我們用 Vue 常規(guī)的標(biāo)簽方式聲明 AUI 的 Slider 組件,則代碼示例如下:

  由于 AUI 組件天然支持配置式開發(fā),除了上面的標(biāo)簽式聲明,AUI 還提供與上述代碼等價的配置式聲明:

  可見配置式聲明沿用 HAE 的方式,將所有配置信息都放在 op 變量里。以下是這兩種聲明方式的詳細(xì)差異:

  如果將兩者放在特定的業(yè)務(wù)領(lǐng)域比較,比如低代碼平臺,則配置式聲明的優(yōu)勢更加明顯,理由如下:

  簡化 DSL 開發(fā)流程:配置式聲明將組件的配置信息集中在一個對象中,低代碼 DSL 開發(fā)人員可以通過修改對象的屬性值來自定義組件的行為和外觀。這種方式避免生成繁瑣的標(biāo)簽嵌套和屬性設(shè)置,簡化了 DSL 的開發(fā)流程。

  提高配置的可復(fù)用性:配置式聲明可以將組件的配置信息抽象為一個可重復(fù)使用的對象,可以在多個組件實例中共享和復(fù)用。低代碼平臺開發(fā)人員可以定義一個通用的配置對象,然后在不同的場景中根據(jù)需要進(jìn)行定制,減少了重復(fù)的代碼編寫和配置調(diào)整。

  動態(tài)生成配置信息:配置式聲明允許低代碼平臺開發(fā)人員使用變量、動態(tài)表達(dá)式和邏輯控制來低代碼組件配置面板生成的配置信息。這樣可以根據(jù)不同的條件和數(shù)據(jù)來動態(tài)調(diào)整組件的配置,增強了組件配置面板的靈活性和適應(yīng)性。

  可視化配置界面:配置式聲明通常與可視化配置界面相結(jié)合,低代碼平臺的使用人員可以通過低代碼的可視化界面直接修改物料組件的屬性值。這種方式使得配置更直觀、易于理解,提高了開發(fā)效率。

  適應(yīng)復(fù)雜業(yè)務(wù)場景:在復(fù)雜的業(yè)務(wù)場景中,組件的配置信息可能會非常繁瑣和復(fù)雜。通過配置式聲明,低代碼物料組件的開發(fā)人員可以更方便地管理和維護(hù)大量的配置屬性,減少了出錯的可能性。

  時間來到2019年,如前面提到的,AUI 進(jìn)入成熟穩(wěn)定期,我們有了時間去思考如何將 jQuery 的 30 萬行代碼重構(gòu)為 Vue 的代碼。同年5月16日,美國商務(wù)部將華為列入出口管制“實體名單”,我們面臨前所未有的困難,保證業(yè)務(wù)連續(xù)性成為我們首要任務(wù)。我們要做最壞的打算,如果有一天所有的主流前端框架 Angular、React、Vue 都不能再繼續(xù)使用,那么重構(gòu)后的 Vue 代碼又將何去何從?

  因此,我們組件的核心代碼要與主流前端框架解耦,這就要求我們不僅僅要重構(gòu)代碼,還要重新設(shè)計架構(gòu)。經(jīng)過不斷的打磨和完善,擁有全新架構(gòu)的 TinyVue 組件庫逐漸浮出水面,以下就是 TinyVue 組件的架構(gòu)圖:

  在這個架構(gòu)下,TinyVue 組件有統(tǒng)一的 API 接口,開發(fā)人員只需寫一份代碼,組件就能支持不同終端的展現(xiàn),比如 PC 端和 Mobile 端,而且還支持不同的 UX 交互規(guī)范。借助 React 框架的 Hooks API 或者 Vue 框架的 Composition API 可以實現(xiàn)組件的核心邏輯代碼與前端框架解耦,甚至實現(xiàn)一套組件庫代碼,同時支持 Vue 的不同版本。

  接下來,我們先分析開發(fā)組件庫面臨的問題,再來探討面向邏輯編程與無渲染組件,最后以實現(xiàn)一個 TODO 組件為例,來闡述我們的解決方案,通過示例代碼展現(xiàn)我們架構(gòu)的四個特性:跨技術(shù)棧、跨技術(shù)棧版本、跨終端和跨 UX 規(guī)范。

  其中,跨技術(shù)棧版本這個特性,已經(jīng)為華為內(nèi)部 IT 帶來巨大的收益。由于 Vue 框架最新的 3.0 版本不能完全向下兼容 2.0 版本,而 2.0 版本又將于2023年12月31日到達(dá)生命周期終止(EOL)。于是華為內(nèi)部 IT 所有基于 Vue 2.0 的應(yīng)用都必須在這個日期之前升級到 3.0 版本,這涉及到幾千萬行代碼的遷移整改,正因為我們的組件庫同時支持 Vue 2.0 和 3.0,使得這個遷移整改的成本大大降低。

  另外,由于前端框架 Angular、React 和 Vue 的大版本不能向下兼容,導(dǎo)致不同版本對應(yīng)不同的組件庫。以 Vue 為例,Vue 2.0 和 Vue 3.0 版本不能兼容,因此 Vue 2.0 的 UI 組件庫跟 Vue 3.0 的 UI 組件庫代碼是不同的,即同一個技術(shù)棧也有不同版本的 UI 組件庫。

  我們將上面不同分類的 UI 組件庫匯總在一張圖里,然后站在組件庫使用者的角度上看,如果要開發(fā)一個應(yīng)用,那么先要從以下組件庫中挑選一個,然后再學(xué)習(xí)和掌握該組件庫,可見當(dāng)前多端多技術(shù)棧的組件庫給使用者帶來沉重的學(xué)習(xí)負(fù)擔(dān)。

  這些 UI 組件庫由于前端框架不同、面向終端不同,常規(guī)的解決方案是:不同的開發(fā)人員來開發(fā)和維護(hù)不同的組件庫,比如需要懂 Vue 的開發(fā)人員來開發(fā)和維護(hù) Vue 組件庫,需要懂 PC 端交互的開發(fā)人員來開發(fā)和維護(hù) PC 組件庫等等。

  很明顯,這種解決方案首先需要不同技術(shù)棧的開發(fā)人員,而市面上大多數(shù)開發(fā)人員只精通一種技術(shù)棧,其他技術(shù)棧則只是了解而已。這樣每個技術(shù)棧就得獨立安排一組人員進(jìn)行開發(fā)和維護(hù),成本自然比單一技術(shù)棧要高得多。另外,由于同一技術(shù)棧的版本升級導(dǎo)致的不兼容,也讓該技術(shù)棧的開發(fā)人員必須開發(fā)和維護(hù)不同版本的代碼,使得成本進(jìn)一步攀升。

  面對上述組件開發(fā)和維護(hù)成本高的問題,業(yè)界還有一種解決方案,即以原生 JavaScript 或 Web Component 技術(shù)為基礎(chǔ),構(gòu)建一套與任何開發(fā)框架都無關(guān)的組件庫,然后再根據(jù)當(dāng)前開發(fā)框架流行的程度,去適配不同的前端框架。比如 Webix 用一套代碼適配任何前端框架,既提供原生 JavaScript 版本的組件庫,也提供 Angular、React 和 Vue 版本的組件庫。

  這種解決方案,其實開發(fā)難度更大、維護(hù)成本更高,因為這相當(dāng)于先要自研一套前端框架,類似于我們以前的 HAE 框架,然后再用不同的前端框架進(jìn)行套殼封裝。顯然,套殼封裝勢必影響組件的性能,而且封閉自研的框架其學(xué)習(xí)門檻、人力成本要高于主流的開源框架。

  當(dāng)前主流的前端框架為 Angular、React 和 Vue,它們提供兩種不同的開發(fā)范式:一種是面向生命周期編程,另一種是面向業(yè)務(wù)邏輯編程?;谶@些前端框架開發(fā)應(yīng)用,頁面上的每個部分都是一個 UI 組件或者實例,而這些實例都是由 JavaScript 創(chuàng)造出來的,都具有創(chuàng)建、掛載、更新、銷毀的生命周期。

  所謂面向生命周期編程,是指基于前端框架開發(fā)一個 UI 組件時,按照該框架定義的生命周期,將 UI 組件的相關(guān)邏輯代碼注冊到指定的生命周期鉤子函數(shù)里。以 Vue 框架的生命周期為例,一個 UI 組件的邏輯代碼可能被拆分到 beforeCreate、created、beforeMount、mounted、beforeUnmount、unmounted 等鉤子函數(shù)里。

  所謂面向邏輯編程,是指在前端開發(fā)的過程中,尤其在開發(fā)大型應(yīng)用時,為解決面向生命周期編程所引發(fā)的問題,提出新的開發(fā)范式。以一個文件瀏覽器的 UI 組件為例,這個組件具備以下功能:

  假設(shè)這個組件按照面向生命周期的方式開發(fā),如果為相同功能的邏輯代碼標(biāo)上一種顏色,那將會是下圖左邊所示??梢钥吹剑幚硐嗤δ艿倪壿嫶a被強制拆分在了不同的選項中,位于文件的不同部分。在一個幾百行的大組件中,要讀懂代碼中一個功能的邏輯,需要在文件中反復(fù)上下滾動。另外,如果我們想要將一個功能的邏輯代碼抽取重構(gòu)到一個可復(fù)用的函數(shù)中,需要從文件的多個不同部分找到所需的正確片段。

  如果用面向邏輯編程重構(gòu)這個組件,將會變成上圖右邊所示。可以看到,與同一個功能相關(guān)的邏輯代碼被歸為了一組:我們無需再為了一個功能的邏輯代碼在不同的選項塊間來回滾動切換。此外,我們可以很輕松地將這一組代碼移動到一個外部文件中,不再需要為了抽象而重新組織代碼,從而大大降低重構(gòu)成本。

  早在2018年10月,React 推出了 Hooks API,這是一個重要的里程碑,對前端開發(fā)人員乃至社區(qū)生態(tài)都產(chǎn)生了深遠(yuǎn)的影響,它改變了前端開發(fā)的傳統(tǒng)模式,使得函數(shù)式組件成為構(gòu)建復(fù)雜 UI 的首選方式。到了2019年初,Vue 在研發(fā) 3.0 版本的過程中也參考了 React 的 Hooks API,并且為 Vue 2.0 版本添加了類似功能的 Composition API。

  當(dāng)時我們正在規(guī)劃新的組件架構(gòu),在了解 Vue 的 Composition API 后,意識到這個 API 的重要性,它就是我們一直尋找的面向邏輯編程。同時,我們也發(fā)現(xiàn)業(yè)界有一種新的設(shè)計模式 —— 無渲染組件,當(dāng)我們嘗試將兩者結(jié)合在一起,之前面臨的問題隨即迎刃而解。

  無渲染組件其實是一種設(shè)計模式。假設(shè)我們開發(fā)一個 Vue 組件,無渲染組件是指這個組件本身并沒有自己的模板(template)以及樣式。它裝載的是各種業(yè)務(wù)邏輯和狀態(tài),是一個將功能和樣式拆開并針對功能去做封裝的設(shè)計模式。這種設(shè)計模式的優(yōu)勢在于:

  邏輯與 UI 分離:將邏輯和 UI 分離,使得代碼更易于理解和維護(hù)。通過將邏輯處理和數(shù)據(jù)轉(zhuǎn)換等任務(wù)抽象成無渲染組件,可以將關(guān)注點分離,提高代碼的可讀性和可維護(hù)性。

  提高可重用性:組件的邏輯可以在多個場景中重用。這些組件不依賴于特定的 UI 組件或前端框架,可以獨立于界面進(jìn)行測試和使用,從而提高代碼的可重用性和可測試性。

  符合單一職責(zé)原則:這種設(shè)計鼓勵遵循單一職責(zé)原則,每個組件只負(fù)責(zé)特定的邏輯或數(shù)據(jù)處理任務(wù)。這樣的設(shè)計使得代碼更加模塊化、可擴展和可維護(hù),減少了組件之間的耦合度。

  更好的可測試性:由于無渲染組件獨立于 UI 進(jìn)行測試,可以更容易地編寫單元測試和集成測試。測試可以專注于組件的邏輯和數(shù)據(jù)轉(zhuǎn)換,而無需關(guān)注界面的渲染和交互細(xì)節(jié),提高了測試的效率和可靠性。

  提高開發(fā)效率:開發(fā)人員可以更加專注于業(yè)務(wù)邏輯和數(shù)據(jù)處理,而無需關(guān)心具體的 UI 渲染細(xì)節(jié)。這樣可以提高開發(fā)效率,減少重復(fù)的代碼編寫,同時也為團(tuán)隊協(xié)作提供了更好的可能性。

  比如下圖的示例,兩個組件 TagsInput A 和 TagInput B 都有相似的功能,即提供 Tags 標(biāo)簽錄入、刪除已有標(biāo)簽兩種能力。雖然它們的外觀截然不同,但是錄入標(biāo)簽和刪除標(biāo)簽的業(yè)務(wù)邏輯是相同的,是可以復(fù)用的。無渲染組件的設(shè)計模式將組件的邏輯和行為與其外觀展現(xiàn)分離。當(dāng)組件的邏輯足夠復(fù)雜并與它的外觀展現(xiàn)解耦時,這種模式非常有效。

  單純使用面向邏輯的開發(fā)范式,僅僅只能讓相同的業(yè)務(wù)邏輯從原本散落到生命周期各個階段的部分匯聚到一起。無渲染組件的設(shè)計模式的實現(xiàn)方式有很多種,比如 React 中可以使用 HOC 高階函數(shù),Vue 中可以使用 scopedSlot 作用域插槽,但當(dāng)組件業(yè)務(wù)邏輯日趨復(fù)雜時,高階函數(shù)和作用域插槽會讓代碼變得難以理解和維護(hù)。

  要實現(xiàn)組件的核心邏輯代碼與前端框架解耦,實現(xiàn)跨端跨技術(shù)棧,需要同時結(jié)合面向邏輯的開發(fā)范式與無渲染組件的設(shè)計模式。首先,按照面向邏輯的開發(fā)范式,通過 React 的 Hooks API,或者 Vue 的 Composition API,將與前端框架無關(guān)的業(yè)務(wù)邏輯和狀態(tài)拆離成相對獨立的代碼。接著,再使用無渲染組件的設(shè)計模式,將組件不同終端的外觀展現(xiàn),統(tǒng)一連接到已經(jīng)拆離相對獨立的業(yè)務(wù)邏輯。

  接下來,我們以開發(fā)一個 TODO 組件為例,講解基于新架構(gòu)的組件如何實現(xiàn)跨端跨技術(shù)棧。假設(shè)該組件 PC 端的展示效果如下圖所示:

  添加待辦事項:在輸入框輸入待辦事項信息,點擊右邊的 Add 按鈕后,下面待辦事項列表將新增一項剛輸入的事項信息。

  刪除待辦事項:在待辦事項列表里,選擇其中一個事項,點擊右邊的X按鈕后,該待辦事項將從列表里清除。

  移動端展示:當(dāng)屏幕寬度縮小時,組件將自動切換成如下 Mobile 的展示形式,功能仍然保持不變,即輸入內(nèi)容直接按回車鍵添加事項,點擊 X 刪除事項。

  這個 TODO 組件的實現(xiàn)分為 Vue 版本和 React 版本,即支持兩個不同的技術(shù)棧。以上特性都復(fù)用一套 TODO 組件的邏輯代碼。這套 TODO 組件的邏輯代碼以柯里化函數(shù)形式編寫。柯里化(英文叫 Currying)是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)的技術(shù)。舉一個簡單的例子:

  本來應(yīng)該一次傳入兩個參數(shù)的 add 函數(shù),柯里化函數(shù)變成先傳入 x 參數(shù),返回一個包含 y 參數(shù)的函數(shù),最終執(zhí)行兩次函數(shù)調(diào)用后返回相同的結(jié)果。一般而言,柯里化函數(shù)都是返回函數(shù)的函數(shù)。

  回到 TODO 組件,按照無渲染組件的設(shè)計模式,首先寫出不包含渲染實現(xiàn)代碼,只包含純業(yè)務(wù)邏輯代碼的函數(shù),以 TODO 組件的添加和刪除兩個功能為例,如下兩個柯里化函數(shù):

  可以看到這兩個組件的邏輯函數(shù),沒有外部依賴,與技術(shù)棧無關(guān)。這兩個邏輯函數(shù)會被組件的 Vue 和 React 的 Renderless 函數(shù)調(diào)用。其中 Vue 的 Renderless 函數(shù)部分代碼如下:

  可以看到,TODO 組件的兩個邏輯函數(shù) addTag 和 removeTag 都有被調(diào)用,分別返回兩個函數(shù)并賦值給 api 對象的兩個同名屬性。而這個技術(shù)棧適配層代碼里的 Renderless 函數(shù),不包含組件邏輯,只用來抹平不同技術(shù)棧的差異,其內(nèi)部按照面向業(yè)務(wù)邏輯編程的方式,分別調(diào)用 React 框架的 Hooks API 與 Vue 框架的 Composition API,這里要保證組件邏輯 addTag 和 removeTag 的輸入輸出統(tǒng)一。

  上述 Vue 和 React 適配層的 Renderless 函數(shù)會被與技術(shù)棧強相關(guān)的 Vue 和 React 組件模板代碼所引用,只有這樣才能充分利用各主流前端框架的能力,避免重復(fù)造框架的輪子。以下是 Vue 頁面引用 Vue 適配層 Renderless 函數(shù)的代碼:

  至此已完成 TODO 組件支持跨技術(shù)棧、復(fù)用邏輯代碼。根據(jù)無渲染組件的設(shè)計模式,前面已經(jīng)分離組件邏輯,現(xiàn)在還要支持組件不同的外觀。TODO 組件要支持 PC 端和 Mobile 兩種外觀展示,即組件結(jié)構(gòu)支持 PC 端和 Mobile 端。所以我們在 Vue 里要拆分為兩個頁面文件,分別是 pc.vue 和 mobile.vue,其中 pc.vue 文件里的 template 組件結(jié)構(gòu)如下:

  由上可見,PC 端和 Mobile 的組件結(jié)構(gòu)雖然不一樣,但是都引用相同的接口,這些接口就是 TODO 組件邏輯函數(shù)輸出的內(nèi)容。

  由上可見,Vue 和 React 的 PC 端及 Mobile 端的結(jié)構(gòu)基本一樣,主要是 Vue 和 React 的語法區(qū)別,因此同時開發(fā)和維護(hù) Vue 和 React 組件結(jié)構(gòu)的成本并不高。以下是 TODO 組件示例的全景圖:

  按無渲染組件的設(shè)計模式,首先要將組件的邏輯分離成與技術(shù)棧無關(guān)的柯里化函數(shù)。

  在定義組件的時候,借助面向邏輯編程的 API,比如 React 框架的 Hooks API、Vue 框架的 Composition API,將組件外觀與組件邏輯完全解耦。

  按不同終端編寫對應(yīng)的組件模板,再利用前端框架提供的動態(tài)組件,實現(xiàn)動態(tài)切換不同組件模板,從而滿足不同外觀的展示需求。

  雖然在 HAE 自研階段,我們實現(xiàn)的數(shù)據(jù)雙向綁定、面向?qū)ο蟮?JS 庫、配置式開發(fā)的注冊表等特性,隨著前端技術(shù)的高速發(fā)展現(xiàn)在已經(jīng)失去存在的意義,但是在 AUI 階段探索的新思路新架構(gòu),經(jīng)過大量的業(yè)務(wù)落地驗證,再次推動前端領(lǐng)域的創(chuàng)新。TinyVue 繼承了 HAE、AUI 的基因,所有的新技術(shù)都從業(yè)務(wù)中來,到業(yè)務(wù)中去。而且,在這個過程中,我們通過不斷吸收、融合開源社區(qū)的最佳實踐和創(chuàng)新,不斷提升自身的核心競爭力。