大前端公共知識雜談

王下邀月熊_Chevalier發表於2017-06-21

  近年來,隨著移動化聯網浪潮的洶湧而來與瀏覽器效能的提升,iOS、Android、Web 等前端開發技術各領風騷,大前端的概念也日漸成為某種共識。其中特別是 Web 開發的領域,以單頁應用為代表的富客戶端應用迅速流行,各種框架理念爭妍鬥豔,百花競放。而 Web 技術的蓬勃發展也催生了一系列跨端混合開發技術,希望能夠結合 Web 的開發便捷性與原生應用的高效能性;其中以 Cordova、PWA 為代表的方向致力於為 Web 應用盡可能新增原生體驗,而以 NativeScript、ReactNative、Weex 為代表的利用 Web 技術或者理念開發原生應用。平心而論,無論哪一種開發領域或者技術,他們本質上都是進行圖形使用者介面(GUI)應用程式的開發,面對的問題、思考的方式、架構的設計很大程度上仍然可以回溯到當年以 MFC、Swing、WPF 為主導的桌面應用程式開發時代,其術不同而道相似。

  任何的前端開發學習中,我們都需要掌握基本的程式語言語法與介面;譬如在 Android 開發中使用的 Java 或者 Kotlin,在 iOS 開發中使用的 Objective-C 或者 Swift,在 Web 開發中使用的 JavaScript、HTML 與 CSS 等。程式語言的學習中我們往往關注於語法基礎、資料結構、功能呼叫、泛型程式設計、超程式設計等內容,譬如如何宣告表示式、如何理解作用域與閉包、如何進行基本的流程控制與異常處理、如何實踐物件導向程式設計、如何進行網路請求通訊等等。接下來我們就需要了解如何構建基礎的介面,譬如利用 HTML 與 CSS 繪製簡單 Web 頁面、利用程式碼建立並使用簡單的 Activity、利用 StoryBoard 快速構建介面原型等等。然後我們需要去學習使用常見的系統功能,譬如如何進行網路互動,如何訪問遠端的 RESTful 介面以獲取需要的資料、如何讀取本地檔案或者利用 SharedPreference、localStorage、CoreData 來存取資料、如何進行元件間或者應用間資訊互動等內容。到這裡我們已經能夠進行基礎的介面開發,並且為其增添必要的特性,不過在真實的專案中我們往往還會用到很多的元件或者外掛,iOS 或者 Android 中為我們提供了豐富的 SDK,譬如 UITableView 或者 RecycleView 可以幫助我們快速構建高效能列表元件,Android 5.0 之後預設的 Material Design 也是非常優秀的介面樣式設計指南;而 Web 開發中我們往往需要引入第三方模式庫,譬如著名的 BootStrap、React Material UI、Vue element 都為我們提供了很多預置的樣式元件,react-virtualized 也為我們提供了高效能的類似於 ListView 這樣的部分項渲染機制。然後我們需要將應用真實地釋出給使用者使用,我們需要考慮很多工程實踐的問題,譬如如何進行測試與除錯、如何進行效能優化並且在生產環境下完成應用狀態跟蹤、熱更新等操作、如何統一開發團隊的程式碼風格與約定等等;這裡 Web 因為其特性而自帶了熱更新的功能,而在 Android 或者 iOS 我們則可以利用外掛化技術或者 JSPatch 來實現熱更新。Java 與 Swift 都是強型別語言,其能夠在編譯階段幫開發者排查問題減少潛在風險;而我們也可以使用 TypeScript 或者 Flow 為 JavaScript 新增靜態型別檢測的特性,在 VSCode 等現代編輯器中同樣可以達到類似於 Android Studio、XCode 中的即時檢查與提示的功能。最後,隨著應用功能的增加、程式碼庫的擴充套件,我們需要考慮整體的應用架構與工程化的問題;在應用架構中我們往往需要考慮模組化、元件化以及狀態管理等多個方面,選擇合適的 MVC、MVP、MVVM、Flux、VIPER 等不同的架構模式來引導應用中的程式碼組織與職責分割;我們也需要考慮選擇合適的構建與部署工具來簡化或者自動化應用釋出流程,在 Android 開發中我們會選擇 Gradle 及其自帶的多模組特性來管理依賴與分割程式碼,而在 Web 中我們可以使用 Webpack、Rollup 等工具來自動處理依賴並且進行構建,iOS 中我們也可以選擇 CoocaPods。

  到這裡我們會發現雖然具體的程式碼實現、使用的技術不同,但是 Android、iOS 以及 Web 乃至於 React Native 等開發中,我們需要解決的問題、能夠用到的架構設計模式都是可以相互借鑑的。在我們從某個領域遷移到其他領域時,我們能很方便地知道應該學習些什麼,不同的技術、工具他們的職責是什麼,應該選擇怎樣的架構或者設計模式。古語云,欲窮千里目,更上一層樓,我們想要真正掌握某種客戶端開發技術,最好是要了解我們應該掌握哪些方面,本文即是對筆者日前總結出的泛前端知識圖譜(Web/iOS/Android/RN) 的簡要闡釋。

 程式語言

  程式語言的學習是我們進入軟體世界的基礎階梯,著名的 Code Complete 一書中提到:Program into Your Language, Not in it. 我們不應該將自己的程式設計思維侷限於掌握的語言提供的那些特性或者概念,而是能夠理解這些語法特性背後能提供的抽象功能與原理,從而能夠根據自己想要達到的目標選擇最合適的程式語言。而從另一個角度來看,無論哪一門程式語言的學習也是具有極大的共性,從嚴謹而又被詬病過度冗餘的 Java 到需要用遊標卡尺的 Python,從掙扎著一路向前的 JavaScript 到含著金湯匙出生的 Swift、Rust,我們都能夠發現其中的相通與互相借鑑之處。

  語法基礎

  任何一門程式語言的學習都需要從基本的表示式(Expression)語法開始學習,我們需要了解如何去宣告與使用變數、如何為這些變數賦值、如何使用運算子進行簡單的變數操作等等。在很多語言之中都有所謂的傳值還是傳引用的思量,譬如 Java 與 JavaScript 本質上就是 Pass-by-Value 的語言,只不過會將複雜物件的引用值傳遞給目標變數。這個特性又引發了所謂淺複製與深複製、如何進行復合型別深拷貝等等需要注意的技術點。除此之外,作用域與閉包也是很多語言學習中重點討論的內容,在 JavaScript 與 Python 的學習中我們就會經常討論如何利用閉包來儲存外部變數,或者在迴圈中避免閉包帶來的意外變數值。表示式是一門程式語言語法基礎的重要組成部分,接下來我們就需要去學習流程控制與異常處理、函式定義與呼叫、類與物件、輸入輸出流、模組等內容。流程控制的典型代表就是分支選擇與迴圈,譬如不同的語言都為我們提供了基礎 for 迴圈或者更方便地 for-in 迴圈,而在 JavaScript 中我們還可以使用 forEach 與 for-of 迴圈,Java 8 之後我們也可以基於 Stream API 中的 forEach 編寫宣告式地迴圈執行體,而 Python 中的列表推導也可以看做便捷的迴圈實現方式。異常處理也是各個程式語言的重要組成部分,合理的異常處理有助於增強應用的魯棒性;不過很多時候會出現濫用異常的情況,我們只是一層一層地丟擲而並未真正地去處理或者利用這些異常。Java 中將異常分為了受控異常與不受控異常這兩類,雖然 JavaScript 等語言中並未在資料型別中有所區分,但是卻可以引入這種分類方式來進行不同的異常處理;有時候 Let it Crash 也是不錯的設計模式。

  Eric Elliott 曾在博文中提及,軟體開發實際就是 Function Composition 與 DataStructure Design;函式或者方法是軟體系統的重要基石與組成。我們需要了解如何去定義函式,包括匿名函式以及 Lambda 表示式等;儘管 Java 中的 Lambda 表示式是對於 FunctionalInterface 的實現,但是鑑於其表現形式我們也可以將其劃歸到函式這個知識類別中。接下來我們需要了解如何定義與傳入函式引數,在 C 這樣的語言中我們會去關心指標傳遞的不同姿勢;而在 JavaScript 中我們常常會關心如何設定預設引數,無論是使用物件解構還是可選引數,都各有利弊。Objective-C 與 Swift 中提供的外部引數就是不錯的函式自描述,Java 或 Python 中提供的不定引數也能夠幫我們更靈活地定義引數,在 JavaScript 中我們則可以通過擴充套件操作符實現類似的效果。然後我們就需要去考慮如何呼叫函式,最典型就是就是 JavaScript 中函式呼叫的四種方式,我們還需要去關心呼叫時函式內部的 this 指標指向。而裝飾器或者註解能幫我們更好地組織程式碼,以類似於高階函式的方式如洋蔥圈般一層一層地剝離與抽象業務邏輯。最後在函式這部分我們還需要關心下迭代器與生成器,它們是不錯的非同步實現模式或者流資料構建工具。

  近幾年隨著前端富客戶端應用的迅猛發展與服務端併發程式設計的深入應用,函數語言程式設計以及 Haskell 這樣的函數語言程式設計語言也是引領風騷。儘管物件導向程式設計也有著很多其他被人詬病的地方,但是在大型複雜業務邏輯的應用開發中我們還是會傾向使用物件導向程式設計的正規化;這就要求我們對於類與物件的基本語法有所掌握。我們首先要去了解如何定義類,定義類的屬性、方法以及使用訪問修飾符等方式進行訪問控制。其次我們需要了解如何從類中例項化出物件,如何在具體的語言中實踐單例模式等。然後我們就需要去了解物件導向的繼承與多型的特性,應該如何實現類繼承,子類與父類在靜態屬性、靜態方法、類屬性、建構函式上的呼叫順序是怎樣的;以及如何利用純虛擬函式、抽象類、介面、協議這些不同的關鍵字在具體語言中實現多型與約定。最後我們還需要去關注下語言是否支援內部類,譬如 Java 就分為了靜態內部類、成員內部類、區域性內部類與匿名內部類這四種不同的分類。在整個語法基礎部分的最後,我們還需要去了解下輸入輸出流與模組化相關的知識,譬如 Java 9 中即將推出 JPMS 模組化系統,而 JavaScript 的模組化標準則歷經了 CommonJS、AMD、UMD、ES6 Modules 等多輪變遷。

  資料結構與功能

  語法基礎是我們掌握某門程式語言的敲門磚,而學習內建的資料結構與功能語法則是能夠用該語言進行實際應用開發的重要前提。在資料結構的學習中我們首先要對該語言內建的資料型別有所概覽,我們要了解如何進行常見的型別與值判斷以及型別間轉換;譬如如何進行引用與值的等價性判斷、如何進行動態型別檢查、如何對複合物件的常用屬性進行判斷等等。很多程式語言中會將資料型別劃分為原始型別(Primitive)與複合型別(Composite),不過這裡為了保證通用性還是將學習複雜度較低的資料型別劃歸到基本型別中。常見的基本型別囊括了數值型別、空型別、布林型別、可選型別(Optional)以及列舉型別(Enum)等等。學習數值型別的時候我們還需要了解如何進行隨機數生成、如何進行常見的科學計算,這也是基礎的數值理論演算法的重要組成。JavaScript 中提供了 undefined 與 null 兩個關鍵字,二者都可以認為是空型別不過又有所區別;而可選型別則能夠幫我們更好地處理可能為空地物件,避免很多的執行時錯誤。接下來我們就要將目光投注於字串型別上,我們需要了解如何建立、刪除、複製、替換某個字串或者其他內容;很多語言也提供了模板字串或者格式化字串的方式來建立新字串。我們還需要知道如何對字串進行索引遍歷,如何對字串進行常見的型別編碼以及如何實踐模式匹配。模式匹配中最直接的方式就是使用正規表示式,這也是我們應用開發中經常會使用到的技術點。除此之外我們還需要關注字串校驗、以及如何進行高效模糊搜尋等等內容;我們也可以學習使用 KMP、Sunday 等常見的模式匹配演算法來處理搜尋問題。

  然後我們需要學習常見的時間與日期處理方式,瞭解如何時間戳、時區、RFC2822、ISO8601 這些基礎的時間與日期相關的概念,瞭解如何從時間戳或者時間字串中解析出當前程式語言支援的時間與日期物件。我們還需要了解時區轉換、時間比較以及如何格式化地展示時間等內容,有時候我們還需要利用日曆等物件進行事件的增減以及偏移計算。接下來就是非常重要的集合型別,無論哪種程式語言都會提供類似於 Array、List、Set、Dictionary、Map 等相關的資料結構實現,而我們也就需要去了解這些常見集合型別中的增刪復替以及索引遍歷這些基礎操作以及每個集合的特點;譬如對於序列型別我們要能熟練使用 map、reduce、filter、sort 這些常見的變換進行序列變換與生成。進階而言的話我們可以多瞭解下這些資料結構的底層演算法實現,譬如 Java 8 中對於 HashMap 的連結串列/紅黑樹實現,或者 V8 中是如何利用 Hidden Class 進行快速索引的。接下來的話我們可以對於像 Java 中 SteamAPI 或者各種語言的 Immutable 物件的實現方式有所瞭解,還有就是常見的 JSON、XML、CSV 這些型別的序列化與反序列化操作庫也是實際開發中經常用到的。

  接下來我們就需要對語言提供的常用外部功能相關的 API 或者語法有所掌握,主要也是分為儲存、網路與系統程式這三個部分。在儲存部分我們需要掌握如何與 MySQL、Redis、Mongodb 等關係型或者非關係型資料庫進行資料互動,掌握如何對檔案系統進行如檔案定址、檔案監控等操作,並且還需要能夠使用一些譬如 Java 堆外儲存這樣的應用內快取來存放資料。而網路部分我們應該掌握如何利用 HTTP 客戶端進行網路互動、如何使用相對底層的 Socket 套接字建立 TCP 連線、或者使用語言內建的一些遠端呼叫框架與遠端服務進行互動。最後我們需要對如何利用語言進行系統程式操作有所瞭解,本部分筆者認為最重要的當屬併發程式設計相關知識。在而今伺服器效能不斷提升、處理的資料量越來越多的情況下,我們不可避免地需要使用併發操作來提高應用吞吐量。併發程式設計領域我們應該去學習如何使用執行緒、執行緒池或者協程來實現併發,如何利用鎖、事務等方式進行併發控制並保持資料一致性,如何使用回撥、Promise、Generator、Async/Await 等非同步程式設計模式。除此之外,我們還需要對切面程式設計、系統呼叫以及本地跨語言呼叫有所瞭解。

  工程實踐與進階

  程式語言初學階段的最後我們需要了解下工程實踐以及一些偏原理與底層實現的進階內容。首先開發者應當對具體程式語言中如何實現 S.O.L.I.D 程式設計原則與數十種設計模式有所瞭解,當然也不能邯鄲學步只求形似,而是能夠根據業務功能需求靈活地選擇適用的正規化。而在團隊開發中我們往往還需要統一團隊內的樣式指南,包括程式碼風格約定中常見的命名約定、文件與註釋約定、專案與模組的目錄架構以及語法檢查規範等。接下來我們還需要對語言或者常用開發工具的除錯方式有所瞭解,掌握基本的單步除錯等技巧,並且能夠為程式碼編寫合適的單元測試用例。工程實踐方面的最後則是要求我們對程式碼效能優化所有了解,儘量避免反模式。
進階內容的話則相對更加地抽象或者需要花費更多的精力去學習,其中包括泛型程式設計、超程式設計、函數語言程式設計、響應式程式設計、記憶體管理、資料結構與演算法等幾個部分。泛型程式設計與超程式設計中的反射、程式碼生成、依賴注入等還是屬於語言本身提供的語法特性之一,而函數語言程式設計與響應式程式設計則偏向於實際應用開發中有所偏愛的開發正規化。即使 Java 這樣純粹的物件導向的語言,當我們借鑑純函式、不可變物件、高階函式、Monad 等函數語言程式設計中常見的名詞時,也能為程式碼優化開闢新的思路。響應式程式設計是非常不錯的非同步程式設計正規化,這裡我們還需要注意下併發程式設計與非同步程式設計之間的差異。而記憶體管理則有助於我們理解程式語言執行地底層機制,譬如對於 JVM 或者 V8 的記憶體結構、記憶體分配、垃圾回收機制有所瞭解的話能夠反過來有助於我們編寫高效能地應用程式,並且對於線上應用錯誤的除錯也能更加得心應手。

 介面基礎

  使用者介面是前端應用程式的核心組成部分,而我們涉足前端開發的第一步往往也就是從簡單的介面搭建開始。我們可能是在 Android 中編寫簡單的基於 XML 佈局的 Activity,在 iOS 中利用 StoryBoard 快速構建導航介面,或者在 Web 中使用某個框架實現 TODOList。而介面開發最基礎的部分就是佈局與定位,無論在何端開發中我們往往都會使用相對佈局、絕對佈局、彈性佈局、網格佈局等佈局方式;並且面向多尺寸的螢幕我們往往也需要進行響應式佈局的考慮,從橫豎屏響應式切換到不同解析度下的佈局與尺寸的調整,都是為了給予使用者較好的使用體驗。而瞭解了佈局與定位之後,我們往往就需要來學習如何使用基本的介面容器,譬如常見的滾動檢視、導航檢視、頁卡檢視與伸縮檢視。Android 與 iOS 往往也為我們對這些基本容器進行了較好地封裝,而 Web 中則往往需要我們自己動手去實現相應功能。譬如在滾動檢視中,我們需要去提供常見的滾動事件控制,典型的有如何在不同環境下保證平滑滾動體驗、如何設定優美的滾動條、如何設定滾動監聽等等。除此之外,我們往往還需要針對列表或者長閱讀介面封裝一些高階事件響應,譬如上拉載入、下拉重新整理以及無限滾動時需要的滾動觸發規則實現。作為最常見的使用者互動方式之一,無論是在移動端還是桌面端,我們也都需要實現一些優美的動畫;譬如視差滾動就可以給使用者帶來不一樣的視覺感受,而像 Swiper 這樣的整頁滾動則是很好地產品展示或者講演頁的互動方式。

  在基礎的介面容器使用中我們已經接觸了一些使用者互動的監聽與響應的實現,接下來我們則是需要深入全面地瞭解使用者互動相關內容。最基礎的我們需要了解常用事件與手勢操作,瞭解如何進行事件監聽與繫結、如何捕獲事件並且進行分發、如何進行縮放、拖拽、搖晃等複雜手勢動作地監聽與識別、如何響應鍵盤事件並且進行響應處理。除此之外,筆者將音視訊錄製與播放,指紋、計步器等感測器的使用,本地通知與遠端推送等內容也都歸納於了使用者互動這個分類下。在 Android 與 iOS 開發中相信對於這些 API 的使用並不會陌生,而隨著 HTML5 的流行以及現代瀏覽器的發展,相信未來 Web 應用也會越來越多地新增這些與系統層面進行互動地功能。我們在本部分還需要了解下動畫與變換、繪圖及資料視覺化等相關內容。常見的動畫引擎包含了屬性控制與幀動畫兩種方式,前者更趨向於指令式程式設計而後者則適用於宣告式程式設計;除了瞭解這些基礎的語法,我們還需要對常用的動畫進行收集與彙總,以便在專案開發中能夠靈活應用。而隨著大資料時代的到來,資料視覺化相關應用也成了前端開發常見的任務之一。在這個部分,我們需要對 SVG、Canvas、WebGL 等相關繪畫基礎有所瞭解,能夠運用 D3.js 或者其他類似的庫進行簡單圖形繪製。並且我們要能夠利用 ECharts 等優秀的外部繪相簿進行散點圖、折線圖、流程圖等常見型別圖表進行繪製。最後,地圖以及相關技術也是我們需要去了解的,作為開發者我們要能夠基於百度地圖等第三方 API 或者 SDK 開發導航、地理位置資訊視覺化等相關的功能。

 系統功能

  與介面基礎相對的就是常見的系統功能以及 API 的使用語法,其主要分為系統與程式、資料儲存以及網路互動這三個部分。

  程式與儲存

  在開發多介面應用程式或者利用 Service、ServiceWorker 等方式啟動後臺執行緒時,我們就需要考慮如何進行元件間通訊;譬如在 Android 開發中我們可以利用 Otto 等庫以訊息匯流排的方式在 Activity、Fragment、Service 等元件之間傳遞訊息。而在 Android 或者 iOS 開發中我們也常常需要考慮併發程式設計,可能會涉及到如何利用 Thread、GCD 等方式實現多執行緒並行、如何利用 RxJava 等響應式擴充套件優化非同步程式設計模型、如何利用鎖等同步方式進行併發控制等等內容。有時候我們也需要去更多地瞭解系統服務相關的內容,特別是在 Android 或者桌面應用程式開發時,我們需要考慮如何實現守護程式以協調並且保障各個元件的正常執行。在系統與程式部分的最後,我們還需要去接觸些系統輔助相關的功能實現,譬如如何進行執行環境檢測、如何利用 DeepLink 進行 APP 之間跳轉、如何進行應用的許可權管理等等。接下來我們討論下資料儲存部分應該掌握哪些內容,最簡單的就是類似於 SharedPreference、NSUserDefaults、localStorage 這樣的鍵值類儲存;複雜一點的情況我們可能會利用到 SQLite 或者 IndexedDB 這樣的簡化關係型或者文件型資料庫,有時候 Realm 這樣的第三方解決方案也是不錯的選擇。很多時候我們還需要了解如何控制快取或者剪貼簿中的內容,以及如何對檔案系統進行基本的操作,譬如讀寫配置檔案與資原始檔、瀏覽列舉檔案系統中的檔案並且根據不同的檔案型別選用不同的處理方式。

  網路互動

  而網路互動部分更多地關注如何與服務端或者第三方系統進行互動,實際上對於如何在需求動態變化的情況下較好地協調服務端與客戶端對於介面的定義是很多專案開發的痛點。不過從基礎使用的角度,我們首先需要了解如何利用網路客戶端進行基於 HTTP 或 HTTPS 的網路請求。這部分我們需要了解如何構造、分析、編碼 URI,如何管理請求頭、設定請求方法與請求引數,如何同步、非同步或者併發地執行請求,如何進行響應解析,如何進行復雜的請求管理等等內容。除了這些,我們還要能夠利用基礎的 Socket 進行通訊,這樣有助於我們理解通訊網路與 TCP/IP 實現原理;我們往往還需要關心如何利用 WebSocket 等技術實現推送與長連線功能,如何進行遠端與本地方法呼叫等等。除了這三個偏功能實現的知識點,我們還可以嘗試去了解下系統的底層設計原理。譬如在 Android 開發中我們可以嘗試去了解 Dalvik 虛擬機器的工作原理,使用 Xposed 或類似工具進行系統層面的一些操作;對於 Web 開發而言我們可以去更多地關注瀏覽器工作原理,瞭解現代瀏覽器的執行機制等等內容。

 介面外掛

  在掌握瞭如何構建基本的介面並且為應用新增必須的功能之後,我們就需要去嘗試進行應用專案開發。每個應用可以按照使用者互動地邏輯切分為多個獨立介面,而每個介面的開發中我們往往又需要編寫導航、選單、列表、表單等等可重複使用的介面外掛。實際上前端開發中最核心的工作之一就是介面外掛的開發,好的開發者能夠在專案開發中沉澱出可複用的介面外掛庫;這類可複用的介面外掛往往會獨立於具體的業務邏輯,其分類自然也應按照顯示或者互動邏輯本身,而不應該受制於不同的業務場景。筆者習慣地會將介面外掛區分為指示器(Indicator)、輸入器(Picker)、列表與表單(TableGrid)、對話方塊(Dialog)、畫廊(Galley)、WebView 等幾個部分。

  指示器與輸入器

  指示器與輸入器算是兩個寬泛的介面外掛分類,最常見的指示器當屬文字顯示類別的外掛,譬如標籤。標籤多用於表單中的輸入域描述、使用者引導等場景,而除了文字標籤之外我們也會使用圖示或者所謂的 Tags。除此之外我們還會關注於 MarkDown 等富文字的展示、如何針對不同螢幕對頁面進行排版與字型設定、如何針對不同地區的使用者進行國際化切換、如何為文字新增合適的動畫等等方面。在應用開發中我們也會新增專門的介紹或引導頁,一方面引導使用者使用,另一方面也可以進行後臺資源請求與處理;譬如我們往往會在應用啟動時設定閃屏頁(Splash),記得最早在 Uber 見到以短視訊為背景的閃屏頁很有耳目一新的感覺。除此之外,我們常見的指示器還包括了進度指示與時間指示這兩種。在進行資料請求或者資料處理等需要使用者等待的場景中,我們往往會給使用者以進度條方式地友好反饋,這種進度條就是典型地進度指示。常用的進度條設計有線性進度條、圓形進度條或者固定在頁首或者頁尾的進度條,有些設計中我們也會以背景投射地方式反饋當前進度,這種方式可能更具有視覺衝擊力。而除了進度條之外,無限迴圈的載入效果、分頁器或者步驟跟蹤顯示器也是常見的進度指示的表現形式之一。而所謂的時間指示即譬如介面上放置的擬物時鐘或者電子時鐘、常見於社交媒體上的時間軸或者日曆效果以及倒計數效果等。

  而輸入器的典型代表則為按鈕與文字輸入,譬如我們除了常見的 Primary、Secondary 按鈕之外,我們可能還會用到懸浮按鈕、可擴充套件的按鈕或者在喜歡與點贊時用到的具有一定動畫效果的按鈕。而文字輸入系列的外掛中,除了常見的文字框或者富文字編輯器,有時我們也需要去編寫具有自動補全或者類似於密碼、勾選之類的特殊格式的輸入框。選擇器也是我們常用到的輸入器之一,譬如開關、單選按鈕、勾選按鈕、分段輸入以及常用於兩個列表互選的左右穿梭器等等。除了這些,搜尋、選單、解鎖介面也是歸屬於輸入器這個類別中。

  列表、畫廊與對話方塊

  在這兩個大類之外算得上最常用的外掛的當屬列表、網格與表單這個系列的控制元件;基本上每個應用都會包含列表或者網格佈局,對於海量資料的列表渲染也是前端常見的挑戰之一。Android 中內建的 RecycleView 與 iOS 中內建的 UITableView 都為我們提供了不錯的懶載入、區域性渲染的功能,而 Web 中我們往往需要自己定製或者尋求第三方庫的幫助。對於列表的互動也是常見問題之一,除了允許使用者正常的點選,我們還需要新增左滑右滑時的反饋、可伸縮或者允許排序、拖拽的方式進行互動,有時候還需要為了列表項新增進出時的轉場動畫,以這種微互動增加整個介面的友好性。最後我們來聊聊畫廊與對話方塊,畫廊最典型的外掛就是提供圖片或者視訊預覽的走馬燈效果的輪播外掛,筆者也是將圖片載入、呈現、處理相關的外掛劃分到了畫廊這一系列外掛中。而在端開發中我們常常需要對相簿或者快取中的圖片進行瀏覽,或者將圖片以瀑布流的方式呈現給使用者,這種性質的外掛也應歸屬到畫廊這一類中。對話方塊的分類則稍顯的有些生硬,譬如 ActionSheet、HUD 是系統提供的訊息提示性質的外掛,這種彈出與顯示層自然會劃歸到對話方塊這個系列的元件中。而在 Web 中我們常常需要自定義的模態對話方塊、覆蓋層也屬於對話方塊系列,有時候我們還需要考慮如何為對話方塊提供拖拽支援,或者在對話方塊顯示和消失之際新增轉場動畫。

 工程化與應用架構

  前面我們討論了開發某個前端應用所需要的必備技能,而在需要持續交付的團隊專案開發中,我們還需要考慮很多工程實踐相關的方法與技巧。指令式程式設計到宣告式程式設計的變化,將更多地功能性工作交於框架處理,而開發人員更加地專注於業務邏輯的實現。

  工程實踐

  程式碼除錯是每個程式設計師都掌握的技能,不過如何較好地除錯程式碼以快速定位錯誤所在卻並不是那麼容易。在開發中我們常常需要熱載入、增量編譯等相關技術來避免過長的等待,而單步除錯則能夠幫助我們梳理程式碼邏輯、循序漸進地發現問題所在。可能 iOS、Android 的開發人員更習慣使用單步除錯,而在 Web 或者 Node.js 開發中我們也應適當地多使用 Chrome 等工具進行程式碼的單步除錯;有時候單步除錯也是不錯的瀏覽分析第三方原始碼庫的方式。另一方面,日誌無論在開發環境還是生產環境中都能夠幫我們記錄應用執行狀態等資訊。接下來我們還要了解應用開發週期中不同階段使用的單元測試、整合測試以及端到端測試的具體的實現方式,在團隊協同開發中統一程式碼風格與約定,能夠利用多種方式對應用進行效能優化,以及在釋出到生產環境之後能夠混淆加密、進行應用更新以及應用狀態跟蹤。

  應用架構

  所謂架構二字,核心即是對於對於富客戶端的程式碼組織/職責劃分,從具體的程式碼分割的角度,即是功能的模組化、介面的元件化、應用狀態管理這三個方面。縱覽這十年內的架構模式變遷,大概可以分為 MV 與 Unidirectional 兩大類,而 Clean Architecture 則是以嚴格的層次劃分獨闢蹊徑。從筆者的認知來看,從 MVC 到 MVP 的變遷完成了對於 View 與 Model 的解耦合,改進了職責分配與可測試性。而從 MVP 到 MVVM,新增了 View 與 ViewModel 之間的資料繫結,使得 View 完全的無狀態化。最後,整個從 MV 到 Unidirectional 的變遷即是採用了訊息佇列式的資料流驅動的架構,並且以 Redux 為代表的方案將原本 MV* 中碎片化的狀態管理變為了統一的狀態管理,保證了狀態的有序性與可回溯性。 實際上從 MVC、MVP 到 MVVM,一直圍繞的核心問題就是如何分割 ViewLogic 與 View,即如何將負責介面展示的程式碼與負責業務邏輯的程式碼進行分割。所謂分久必合,合久必分,從筆者自我審視的角度,發現很有趣的一點。Android 與iOS中都是從早期的用程式碼進行元件新增與佈局到專門的 XML/Nib/StoryBoard 檔案進行佈局,Android 中的 Annotation/DataBinding、iOS 中的 IBOutlet 更加地保證了 View 與 ViewLogic 的分割(這一點也是從元素操作到以資料流驅動的變遷,我們不需要再去編寫大量的 findViewById。而Web的趨勢正好有點相反,無論是 WebComponent 還是 ReactiveComponent 都是將 ViewLogic 與 View 置於一起,特別是 JSX 的語法將 JavaScript 與 HTML 混搭,頗有幾分當年 PHP/JSP 與 HTML 混搭的風味。

  從程式碼組織的角度來看,專案的構建工具與依賴管理工具會深刻地影響到程式碼組織,這一點在功能的模組化中尤其顯著。譬如筆者對於 Android/Java 構建工具的使用變遷經歷了從 Eclipse 到 Maven 再到 Gradle,筆者會將不同功能邏輯的程式碼封裝到不同的相對獨立的子專案中,這樣就保證了子專案與主專案之間的一定隔離,方便了測試與程式碼維護。同樣的,在 Web 開發中從 AMD/CMD 規範到標準的 ES6 模組與 Webpack 編譯打包,也使得程式碼能夠按照功能儘可能地解耦分割與避免冗餘編碼。而另一方面,依賴管理工具也極大地方便我們使用第三方的程式碼與釋出自定義的依賴項,譬如 Web 中的 NPM 與 Bower,iOS 中的 CocoaPods 都是十分優秀的依賴釋出與管理工具,使我們不需要去關心第三方依賴的具體實現細節即能夠透明地引入使用。因此選擇合適的專案構建工具與依賴管理工具也是好的GUI架構模式的重要因素之一。不過從應用程式架構的角度看,無論我們使用怎樣的構建工具,都可以實現或者遵循某種架構模式,筆者認為二者之間也並沒有必然的因果關係。而元件即是應用中使用者互動介面的部分組成,元件可以通過組合封裝成更高階的元件。元件可以被放入層次化的結構中,即可以是其他元件的父元件也可以是其他元件的子元件。根據上述的元件定義,筆者認為像 Activity 或者UIViewController 都不能算是元件,而像 ListView 或者 UITableView 可以看做典型的元件。 我們強調的是介面元件的Composable&Reusable,即可組合性與可重用性。當我們一開始接觸到 Android 或者 iOS 時,因為本身 SDK 的完善度與規範度較高,我們能夠很多使用封裝程度較高的元件;凡事都有雙面性,這種較高程度的封裝與規範統一的 API 方便了我們的開發,但是也限制了我們自定義的能力。同樣的,因為 SDK 的限制,真正意義上可複用/組合的元件也是不多,譬如你不能將兩個 ListView 再組合成一個新的ListView。在 React 中有所謂的 controller-view 的概念,即意味著某個 React 元件同時擔負起 MVC 中 Controller 與 View 的責任,也就是 JSX 這種將負責 ViewLogic 的 JavaScript 程式碼與負責模板的 HTML 混編的方式。

  介面的元件化還包括一個重要的點就是路由,譬如 Android 中的 AndRouter、iOS中的 JLRoutes 都是集中式路由的解決方案,不過集中式路由在 Android 或者 iOS 中並沒有大規模推廣。iOS 中的 StoryBoard 倒是類似於一種集中式路由的方案,不過更偏向於以 UI 設計為核心。筆者認為這一點可能是因為 Android 或者 iOS 本身所有的程式碼都是存放於客戶端本身,而 Web 中較傳統的多頁應用方式還需要使用者跳轉頁面重新載入,而後在單頁流行之後即不存在頁面級別的跳轉,因此在 Web 單頁應用中集中式路由較為流行而 Android、iOS 中反而不流行。所謂可變的與不可預測的狀態時軟體開發中的萬惡之源,我們儘可能地希望元件的無狀態性,那麼整個應用中的狀態管理應該儘量地放置在所謂 High-Order Component 或者 Smart Component 中。在 React 以及 Flux 的概念流行之後,Stateless Component 的概念深入人心,不過其實對於 MVVM 中的 View,也是無狀態的 View。通過雙向資料繫結將介面上的某個元素與 ViewModel 中的變數相關聯,筆者認為很類似於 HOC 模式中的 Container 與 Component 之間的關聯。隨著應用的介面與功能的擴充套件,狀態管理會變得愈發混亂。

相關文章