標籤:

大話大前端時代(一) —— Vue 與 iOS 的組件化

今年大前端的概念一而再再而三的被提及,那麼大前端時代究竟是什麼呢?大前端這個詞最早是因為在阿里內部有很多前端開發人員既寫前端又寫 Java 的 Velocity 模板而得來,不過現在大前端的範圍已經越來越大了,包含前端 + 移動端,前端、CDN、Nginx、Node、Hybrid、Weex、React Native、Native App。筆者是一名普通的全職 iOS 開發者,在接觸到了前端開發以後,發現了前端有些值得移動端學習的地方,於是便有了這個大前端時代系列的文章,希望兩者能相互借鑒優秀的思想。談及到大前端,常常被提及的話題有:組件化,路由與解耦,工程化(打包工具,腳手架,包管理工具),MVC 和 MVVM 架構,埋點和性能監控。筆者就先從組件化方面談起。網上關於前端框架對比的文章也非常多(對比 React,Vue,Angular),不過跨端對比的文章好像不多?筆者就打算以前端和移動端(以 iOS 平台為主)對比為主,看看這兩端的不同做法,並討論討論有無相互借鑒學習的地方。

本文前端的部分也許前端大神看了會覺得比較基礎,如有錯誤還請各位大神不吝賜教。


Vue 篇

一. 組件化的需求

為了提高代碼復用性,減少重複性的開發,我們就把相關的代碼按照 template、style、script 拆分,封裝成一個個的組件。組件可以擴展 HTML 元素,封裝可重用的 HTML 代碼,我們可以將組件看作自定義的 HTML 元素。在 Vue 裡面,每個封裝好的組件可以看成一個個的 ViewModel。

二. 如何封裝組件

談到如何封裝的問題,就要先說說怎麼去組織組件的問題。

如果在簡單的 SPA 項目中,可以直接用 Vue.component 去定義一個全局組件,項目一旦複雜以後,就會出現弊端了:

  1. 全局定義(Global definitions) 強制要求每個 component 中的命名不得重複
  2. 字元串模板(String templates) 缺乏語法高亮,在 HTML 有多行的時候,需要用到醜陋的
  3. 不支持 CSS(No CSS support) 意味著當 HTML 和 JavaScript 組件化時,CSS 明顯被遺漏
  4. 沒有構建步驟(No build step) 限制只能使用 HTML 和 ES5 JavaScript, 而不能使用預處理器,如 Pug (formerly Jade) 和 Babel

而且現在公司級的項目,大多數都會引入工程化的管理,用包管理工具去管理,npm 或者 yarn。所以 Vue 在複雜的項目中用 Vue.component 去定義一個組件的方式就不適合了。這裡就需要用到單文件組件,還可以使用 Webpack 或 Browserify 等構建工具。比如下面這個Hello.vue組件,整個文件就是一個組件。

在單文件組件中,整個文件都是一個 CommonJS 模塊,裡面包含了組件對應的 HTML、組件內的處理邏輯 Javascript、組件的樣式 CSS。

在組件的 script 標籤中,需要封裝該組件 ViewModel 的行為。

  • data 組件的初始化數據,以及私有屬性。
  • props 組件的屬性,這裡的屬性專門用來接收父子組件通信的數據。(這裡可以類比 iOS 裡面的 @property )
  • methods 組件內的處理邏輯函數。
  • watch 需要額外監聽的屬性(這裡可以類比 iOS 裡面的 KVO )
  • computed 組件的計算屬性
  • components 所用到的子組件
  • lifecycle hooks 生命周期的鉤子函數。一個組件也是有生命周期的,有如下這些:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、activated、deactivated、beforeDestroy、destroyed等生命周期。在這些鉤子函數裡面可以加上我們預設的處理邏輯。(這裡可以類比 iOS 裡面的 ViewController 的生命周期 )

如此看來,在 Vue 裡面封裝一個單文件組件,和在 iOS 裡面封裝一個 ViewModel 的思路是完全一致的。接下來的討論無特殊說明,針對的都是單文件組件。

三. 如何劃分組件

一般劃分組件分可以按照以下標準去劃分:

  1. 頁面區域: header、footer、sidebar……
  2. 功能模塊: select、pagination……

這裡舉個例子來說明一起前端是如何劃分組件的。

1. 頁面區域

還是以 objc中國 的首頁頁面為例

我們可以把上面的頁面按照布局,先抽象圖片中間的樣子,然後接著按照頁面的區域劃分組件,最後可以得到最右邊的組件樹。

在 Vue 實例的根組件,載入 layout。

import Vue from vue;nimport store from ./store;nimport router from ./router;nimport Layout from ./components/layout;nnnew Vue({n el: #app,n router,n store,n template: <Layout/>,n components: {n Layoutn }n});nnnJavaScriptn

Copy

根據抽象出來的組件樹,可以進一步的向下細分各個小組件。

layout 下一層的組件是 header、footer、content,這三部分就組成了 layout.vue 單文件組件的全部部分。

上圖就是我們的 layout.vue 的全部實現。在這個單文件組件中裡面引用了三個子組件,navigationBar、footerView、content。由於 content 裡面是又各個路由頁面組成,所以這裡聲明成 router-view。

至於各個子組件的具體實現這裡就不在贅述了,具體代碼可以看這裡navigationBar.vue、footerView、layout.vue

2. 功能模塊

一般項目裡面詳情頁的內容最多,我們就以以 objc中國 的詳情頁面為例

上圖左邊是詳情頁,右圖是按照功能區分的圖,我們把整個頁面劃分為6個子組件。

從上往下依次展開,見上圖。

經過功能上的劃分以後,整個詳情頁面的代碼變的異常清爽,整個頁面就是6個單文件的子組件,每個子組件的邏輯封裝在各自的組件裡面,詳情頁面就是把他們都組裝在了一起,代碼可讀性高,後期維護也非常方便。

詳情頁面具體的代碼在這裡github.com/halfrost/vue

6個子組件的代碼在這裡github.com/halfrost/vue,具體的代碼見鏈接,這裡就不在贅述了。

綜上可以看出,前端 SPA 頁面抽象出來就是一個大的組件樹。

四. 組件化原理

舉個例子:

<!DOCTYPE html>n<html>n <body>n <div id="app">n <parent-component>n </parent-component>n </div>n </body>n <script src="js/vue.js"></script>n <script>n n var Child = Vue.extend({n template: <p>This is a child component !</p>n })n n var Parent = Vue.extend({n // 在Parent組件內使用<child-component>標籤n template :<p>This is a Parent component !</p><child-component></child-component>,n components: {n // 局部註冊Child組件,該組件只能在Parent組件內使用n child-component: Childn }n })n n // 全局註冊Parent組件n Vue.component(parent-component, Parent)n n new Vue({n el: #appn })n n </script>n</html>nnJavaScriptn

Copy

在上面的例子中,在 <parent-component> 父組件裡面聲明了一個 <child-component>,最終渲染出來的結果是:

This is a Parent component !nThis is a child component !nnJavaScriptn

Copy

上述代碼的執行順序如下:

  1. 子組件先在父組件中的 components 中進行註冊。
  2. 父組件利用 Vue.component 註冊到全局。
  3. 當渲染父組件的時候,渲染到 <child-component> ,會把子組件也渲染出來。

值得說明的一點是,Vue 進行模板解析的時候會遵循以下 html 常見的限制:

  • a 不能包含其它的交互元素(如按鈕,鏈接)
  • ul 和 ol 只能直接包含 li
  • select 只能包含 option 和 optgroup
  • table 只能直接包含 thead, tbody, tfoot, tr, caption, col, colgroup
  • tr 只能直接包含 th 和 td

五. 組件分類

組件的種類可分為以下4種:

  1. 普通組件
  2. 動態組件
  3. 非同步組件
  4. 遞歸組件

1. 普通組件

之前講的都是普通的組件,這裡就不在贅述了。

2. 動態組件

動態組件利用的是 is 的特性,可以設置多個組件可以使用同一個掛載點,並動態切換。

var vm = new Vue({n el: #example,n data: {n currentView: homen },n components: {n home: { /* ... */ },n posts: { /* ... */ },n archive: { /* ... */ }n }n})nnn<component v-bind:is="currentView">n <!-- 組件在 vm.currentview 變化時改變! -->n</component>nnJavaScriptn

Copy

現在 <component> 組件的具體類型用 currentView 來表示了,我們就可以通過更改 currentView 的值,來動態載入各個組件。上述例子中,可以不斷的更改 data 裡面的 currentView ,來達到動態載入 home、posts、archive 三個不同組件的目的。

3. 非同步組件

Vue允許將組件定義為一個工廠函數,在組件需要渲染時觸發工廠函數動態地解析組件,並且將結果緩存起來:

Vue.component("async-component", function(resolve, reject){n // async operationn setTimeout(function() {n resolve({n template: <div>something async</div>n });n },1000);n});nnJavaScriptn

Copy

動態組件可配合 webpack 實現代碼分割,webpack 可以將代碼分割成塊,在需要此塊時再使用 ajax 的方式下載:

Vue.component(async-webpack-example, function(resolve) {n // 這個特殊的 require 語法告訴 webpackn // 自動將編譯後的代碼分割成不同的塊,n // 這些塊將通過 ajax 請求自動下載。n require([./my-async-component], resolve)n});nnJavaScriptn

Copy

4. 遞歸組件

如果一個組件設置了 name 屬性,那麼它就可以變成遞歸組件了。

遞歸組件可以利用模板裡面的 name 不斷的遞歸調用自己。

name: recursion-component,ntemplate: <div><recursion-component></recursion-component></div>nnJavaScriptn

Copy

上面這段代碼是一個錯誤代碼,這樣寫模板的話就會導致遞歸死循環,最終報錯 「max stack size exceeded」。解決辦法需要打破死循環,比如 v-if 返回 false。

六. 組件間的消息傳遞和狀態管理

在 Vue 中,組件消息傳遞的方式主要分為3種:

  1. 父子組件之間的消息傳遞
  2. Event Bus
  3. Vuex 單向數據流

1. 父子組件之間的消息傳遞

父子組件的傳遞方式比較單一,在 Vue 2.0 以後,父子組件的關係可以總結為 ** props down, events up **。父組件通過 props 向下傳遞數據給子組件,子組件通過 events 給父組件發送消息。

父向子傳遞

舉個例子:

Vue.component(child, {n // 聲明 propsn props: [msg],n // prop 可以用在模板內n // 可以用 `this.msg` 設置n template: <span>{{ msg }}</span>n})nn<child msg="hello!"></child>nnJavaScriptn

Copy

在 child 組件的 props 中聲明了一個 msg 屬性,在父組件中利用這個屬性把值傳給子組件。

這裡有一點需要注意的是,在非字元串模板中, camelCased (駝峰式) 命名的 prop 需要轉換為相對應的 kebab-case (短橫線隔開式) 命名。

上面這個例子是靜態的綁定,Vue 也支持動態綁定,這裡也支持 v-bind 指令進行動態的綁定 props 。

父向子傳遞是一個單向數據流的過程,prop 是單向綁定的:當父組件的屬性變化時,將傳導給子組件,但是不會反過來。這是為了防止子組件無意修改了父組件的狀態——這會讓應用的數據流難以理解。

另外,每次父組件更新時,子組件的所有 prop 都會更新為最新值。這意味著你不應該在子組件內部改變 prop。Vue 建議子組件的 props 是 immutable 的。

這裡就會牽涉到2類問題:

  1. 由於單向數據流的原因,會導致子組件的數據或者狀態和父組件的不一致,為了同步,在子組件裡面反數據流的去修改父組件的數據或者數據。
  2. 子組件接收到了 props 的值以後,有2種原因想要改變它,第一種原因是,prop 作為初始值傳入後,子組件想把它當作局部數據來用;第二種原因是,prop 作為初始值傳入,由子組件處理成其它數據輸出。

這兩類問題,開發者強行更改,也都是可以實現的,但是會導致不令人滿意的 「後果」 。第一個問題強行手動修改父組件的數據或者狀態以後,導致數據流混亂不堪。只看父組件,很難理解父組件的狀態。因為它可能被任意子組件修改!理想情況下,只有組件自己能修改它的狀態。第二個問題強行手動修改子組件的 props 以後,Vue 會在控制台給出警告。

如果優雅的解決這2種問題呢?一個個的來說:

(1)第一個問題,換成雙向綁定就可以解決。

在 Vue 2.3.0+ 以後的版本,雙向綁定有2種方式

第一種方式:

利用 .sync 修飾符,在 Vue 2.3.0+ 以後作為一個編譯時的語法糖存在。它會被擴展為一個自動更新父組件屬性的 v-on 偵聽器。

// 聲明一個雙向綁定n<comp :foo.sync="bar"></comp>nnn// 上面一行代碼會被會被擴展為下面這一行:n<comp :foo="bar" @update:foo="val => bar = val"></comp>nn// 當子組件需要更新 foo 的值時,它會顯式地觸發一個更新事件:nthis.$emit(update:foo, newValue)nnJavaScriptn

Copy

第二種方式:

自定義事件可以用來創建自定義的表單輸入組件,使用 v-model 來進行數據雙向綁定。

<input :value="value" @input="updateValue($event.target.value)" >nnJavaScriptn

Copy

在這種方式下進行的雙向綁定必須滿足2個條件:

  • 接受一個 value 屬性
  • 在有新的值時觸發 input 事件

官方推薦的2種雙向綁定的方式就是上述2種方法。不過還有一些隱性的雙向綁定,可能無意間就會造成bug的產生。

pros 是單向數據傳遞,父組件把數據傳遞給子組件,需要尤其注意的是,傳遞的數據如果是引用類型(比如數組和對象),那麼默認就是雙向數據綁定,子組件的更改都會影響到父組件裡面。在這種情況下,如果人為不知情,就會出現一些莫名其妙的bug,所以需要注意引用類型的數據傳遞。

(2)第二個問題,有兩種做法:

  • 第一種做法是:定義一個局部變數,並用 prop 的值初始化它:

props: [initialCounter],ndata: function () {n return { counter: this.initialCounter }n}nnJavaScriptn

Copy

  • 第二種做法是:定義一個計算屬性,處理 prop 的值並返回。

props: [size],ncomputed: {n normalizedSize: function () {n return this.size.trim().toLowerCase()n }n}nnJavaScriptn

Copy

父向子傳遞還可以傳遞模板,使用 slot 分發內容。

slot 是 Vue 的一個內置的自定義元素指令。slot 在 bind 回調函數中,根據 name 獲取將要替換插槽的元素,如果上下文環境中有所需替換的內容,則調用父元素的 replaceChild 方法,用替換元素講 slot 元素替換;否則直接刪除將要替換的元素。如果替換插槽元素中有一個頂級元素,且頂級元素的第一子節點為 DOM 元素,且該節點有 v-if 指令,且 slot 元素中有內容,則替換模板將增加 v-else 模板放入插槽中的內容。如果 v-if 指令為 false,則渲染 else 模板內容。

子向父傳遞

子組件要把數據傳遞迴父組件,方式很單一,那利用自定義事件!

父組件使用 (on(eventName) 監聽事件 子組件使用 )emit(eventName) 觸發事件

舉個簡單的例子:

// 在子組件裡面有一個 buttonn<button @click="emitMyEvent">emit</button>nnemitMyEvent() {n this.$emit(my-event, this.hello);n}nnn// 在父組件裡面監聽子組件的自定義事件n<child @my-event="getMyEvent"></child>nngetMyEvent() {n console.log( i got child event );n}nnnJavaScriptn

Copy

這裡也可以通過父子之間的關係進行傳遞數據(直接修改數據),但是不推薦這種方法,例如 this.(parent 或者 this.)children 直接調用父或者子組件的方法,這裡類比iOS裡面的ViewControllers方法,在這個數組裡面可以直接拿到所有 VC ,然後就可以調用他們暴露在.h裡面的方法了。但是這種方式相互直接耦合性太大了。

2. Event Bus

Event Bus 這個概念對移動端的同學來說也比較熟悉,因為在安卓開發中就有這個概念。在 iOS 開發中,可以類比消息匯流排。具體實現可以是通知 Notification 或者 ReactiveCocoa 中的信號傳遞。

Event Bus 的實現還是藉助 Vue 的實例。新建一個新的 Vue,專門用來做消息匯流排。

var eventBus = new Vue()nn// 在 A 組件中引入 eventBusneventBus.$emit(myEvent, 1)nn// 在要監聽的組件中監聽neventBus.$on(id-selected, () => {n // ...n})nnJavaScriptn

Copy

3. Vuex 單向數據流

由於本篇文章重點討論組件化的問題,所以這裡 Vuex 只是說明用法,至於原理的東西之後會單獨開一篇文章來分析。

這一張圖就描述了 Vuex 是什麼。Vuex 專為 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。

上圖中箭頭的指向就描述了數據的流向。數據的流向是單向的,從 Actions 流向 State,State 中的數據改變了從而影響到 View 展示數據的變化。

從簡單的 Actions、State、View 三個角色,到現在增加了一個 Mutations。Mutations 現在變成了更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。Vuex 中的 mutations 非常類似於事件:每個 mutation 都有一個字元串的 事件類型 (type) 和 一個 回調函數 (handler)。

一般在組件中進行 commit 調用 Mutation 方法

this.$store.commit(increment, payload);nnJavaScriptn

Copy

Actions 和 Mutations 的區別在於:

  • Action 提交的是 mutation,而不是直接變更狀態。
  • Action 可以包含任意非同步操作,而 Mutations 必須是同步函數。

一般在組件中進行 dispatch 調用 Actions 方法

this.$store.dispatch(increment);nnJavaScriptn

Copy

Vuex 官方針對 Vuex 的最佳實踐,給出了一個項目模板結構,希望大家都能按照這種模式去組織我們的項目。

├── index.htmln├── main.jsn├── apin│ └── ... # 抽取出API請求n├── componentsn│ ├── App.vuen│ └── ...n└── storen ├── index.js # 我們組裝模塊並導出 store 的地方n ├── actions.js # 根級別的 actionn ├── mutations.js # 根級別的 mutationn └── modulesn ├── cart.js # 購物車模塊n └── products.js # 產品模塊nnJavaScriptn

Copy

關於這個例子的詳細代碼在這裡

七. 組件註冊方式

組件的註冊方式主要就分為2種:全局註冊和局部註冊

1. 全局註冊

利用 Vue.component 指令進行全局註冊

Vue.component(my-component, {n // 選項n})nnJavaScriptn

Copy

註冊完的組件就可以在父實例中以自定義元素 <my-component></my-component> 的形式使用。

// 註冊nVue.component(my-component, {n template: <div>A custom component!</div>n})n// 創建根實例nnew Vue({n el: #examplen})nn<div id="example">n <my-component></my-component>n</div>nnJavaScriptn

Copy

2. 局部註冊

全局註冊組件會拖慢一些頁面的載入速度,有些組件只需要用的到時候再載入,所以不必在全局註冊每個組件。於是就有了局部註冊的方式。

var Child = {n template: <div>A custom component!</div>n}nnew Vue({n // ...n components: {n // <my-component> 將只在父模板可用n my-component: Childn }n})nnJavaScriptn

Copy


iOS 篇

一. 組件化的需求

在 iOS Native app 前期開發的時候,如果參與的開發人員也不多,那麼代碼大多數都是寫在一個工程裡面的,這個時候業務發展也不是太快,所以很多時候也能保證開發效率。

但是一旦項目工程龐大以後,開發人員也會逐漸多起來,業務發展突飛猛進,這個時候單一的工程開發模式就會暴露出弊端了。

  • 項目內代碼文件耦合比較嚴重
  • 容易出現衝突,大公司同時開發一個項目的人多,每次 pull 一下最新代碼就會有很多衝突,有時候合併代碼需要半個小時左右,這會耽誤開發效率。
  • 業務方的開發效率不夠高,開發人員一多,每個人都只想關心自己的組件,但是卻要編譯整個項目,與其他不相干的代碼糅合在一起。調試起來也不方便,即使開發一個很小的功能,都要去把整個項目都編譯一遍,調試效率低。

為了解決這些問題,iOS 項目就出現了組件化的概念。所以 iOS 的組件化是為了解決上述這些問題的,這裡與前端組件化解決的痛點不同。

iOS 組件化以後能帶來如下的好處:

  • 加快編譯速度(不用編譯主客那一大坨代碼了,各個組件都是靜態庫)
  • 自由選擇開發姿勢(MVC / MVVM / FRP)
  • 方便 QA 有針對性地測試
  • 提高業務開發效率

iOS 組件化的封裝性只是其中的一小部分,更加關心的是如何拆分組件,如何解除耦合。前端的組件化可能會更加註重組件的封裝性,高可復用性。

二. 如何封裝組件

iOS 的組件化手段非常單一,就是利用 Cocoapods 封裝成 pod 庫,主工程分別引用這些 pod 即可。越來越多的第三方庫也都在 Cocoapods 上發布自己的最新版本,大公司也在公司內部維護了公司私有的 Cocoapods 倉庫。一個封裝完美的 Pod 組件,主工程使用起來非常方便。

具體如果用 Cocoapods 打包一個靜態庫 .a 或者 framework ,網上教程很多,這裡給一個鏈接,詳細的操作方法就不再贅述了。

最終想要達到的理想目標就是主工程就是一個殼工程,其他所有代碼都在組件 Pods 裡面,主工程的工作就是初始化,載入這些組件的,沒有其他任何代碼了。

三. 如何劃分組件

iOS 劃分組件雖然沒有一個很明確的標準,因為每個項目都不同,劃分組件的粗粒度也不同,但是依舊有一個劃分的原則。

App之間可以重用的 Util、Category、網路層和本地存儲 storage 等等這些東西抽成了 Pod 庫。還有些一些和業務相關的,也是在各個App之間重用的。

原則就是:要在App之間共享的代碼就應該抽成 Pod 庫,把它們作為一個個組件。不在 App 間共享的業務線,也應該抽成 Pod,解除它與工程其他的文件耦合性。

常見的劃分方法都是從底層開始動手,網路庫,路由,MVVM框架,資料庫存儲,加密解密,工具類,地圖,基礎SDK,APM,風控,埋點……從下往上,到了上層就是各個業務方的組件了,最常見的就類似於購物車,我的錢包,登錄,註冊等。

四. 組件化原理

iOS 的組件化是藉助 Cocoapods 完成的。關於 Cocoapods 的具體工作原理,可以看這篇文章《CocoaPods 都做了什麼?》。

這裡簡單的分析一下 pod 進來的庫是什麼載入到主工程的。

pod 會依據 Podfile 文件裡面的依賴庫,把這些庫的源代碼下載下來,並創建好 Pods workspace。當程序編譯的時候,會預先執行2個 pod 設置進來的腳本。

在上面這個腳本中,會把 Pods 裡面的打包好的靜態庫合併到 libPods-XXX.a 這個靜態庫裡面,這個庫是主工程依賴的庫。

上圖就是給主項目載入 Pods 庫的腳本。

Pods 另外一個腳本是載入資源的。見下圖。

這裡載入的資源是 Pods 庫裡面的一些圖片資源,或者是 Boudle 裡面的 xib ,storyboard,音樂資源等等。這些資源也會一起打到 libPods-XXX.a 這個靜態庫裡面。

上圖就是載入資源的腳本。

五. 組件分類

iOS 的組件主要分為2種形式:

  1. 靜態庫
  2. 動態庫

靜態庫一般是以 .a 和 .framework 結尾的文件,動態庫一般是以 .dylib 和 .framework 結尾的文件。

這裡可以看到,一個 .framework 結尾的文件僅僅通過文件類型是無法判斷出它是一個靜態庫還是一個動態庫。

靜態庫和動態庫的區別在於:

  1. .a文件肯定是靜態庫,.dylib肯定是動態庫,.framework可能是靜態庫也可能是動態庫;
  2. 靜態庫在鏈接其他庫的情況時,它會被完整的複製到可執行文件中,如果多個App都使用了同一個靜態庫,那麼每個App都會拷貝一份,缺點是浪費內存。類似於定義一個基本變數,使用該基本變數是是新複製了一份數據,而不是原來定義的;靜態庫的好處很明顯,編譯完成之後,庫文件實際上就沒有作用了。目標程序沒有外部依賴,直接就可以運行。當然其缺點也很明顯,就是會使用目標程序的體積增大。
  3. 動態庫不會被複制,只有一份,程序運行時動態載入到內存中,系統只會載入一次,多個程序共用一份,節約了內存。而且使用動態庫,可以不重新編譯連接可執行程序的前提下,更新動態庫文件達到更新應用程序的目的。

六. 組件間的消息傳遞和狀態管理

之前我們討論過了,iOS 組件化十分關註解耦性,這算是組件化的一個重要目的。iOS 各個組件之間消息傳遞是用路由來實現的。關於路由,筆者曾經寫過一篇比較詳細的文章,感興趣的可以來看這篇文章《iOS 組件化 —— 路由設計思路分析》。

七. 組件註冊方式

iOS 組件註冊的方式主要有3種:

  1. load方法註冊
  2. 讀取 plist 文件註冊
  3. Annotation註解方式註冊

前兩種方式都比較簡單,容易理解。

第一種方式在 load 方法裡面利用 Runtime 把組件名和組件實例的映射關係保存到一個全局的字典里,方便程序啟動以後可以隨時調用。

第二種方式是把組件名和組件實例的映射關係預先寫在 plist 文件中。程序需要的時候直接去讀取這個 plist 文件。plist 文件可以從伺服器讀取過來,這樣 App 還能有一定的動態性。

第三種方式比較黑科技。利用的是 Mach-o 的數據結構,在程序編程鏈接成可執行文件的時候,就把相關註冊信息直接寫入到最終的可執行文件的 Data 數據段內。程序執行以後,直接去那個段內去讀取想要的數據即可。

關於這三種做法的詳細實現,可以看筆者之前的一篇文章《BeeHive —— 一個優雅但還在完善中的解耦框架》,在這篇文章裡面詳細的分析了上述3種註冊過程的具體實現。


總結

經過上面的分析,我們可以看出 Vue 的組件化和 iOS 的組件化區別還是比較大的。

兩者平台上開發方式存在差異

主要體現在單頁應用和類多頁應用的差異。

現在前端比較火的一種應用就是單頁Web應用(single page web application,SPA),顧名思義,就是只有一張Web頁面的應用,是載入單個HTML 頁面並在用戶與應用程序交互時動態更新該頁面的Web應用程序。

瀏覽器從伺服器載入初始頁面,以及整個應用所需的腳本(框架、庫、應用代碼)和樣式表。當用戶定位到其他頁面時,不會觸發頁面刷新。通過 HTML5 History API 更新頁面的 URL 。瀏覽器通過 AJAX 請求檢索新頁面(通常以 JSON 格式)所需的新數據。然後, SPA 通過 JavaScript 動態更新已經在初始頁面載入中已經下載好的新頁面。這種模式類似於原生手機應用的工作原理。

但是 iOS 開發更像類 MPA (Multi-Page Application)。

往往一個原生的 App ,頁面差不多應該是上圖這樣。當然,可能有人會說,依舊可以把這麼多頁面寫成一個頁面,在一個 VC 裡面控制所有的 View,就像前端的 DOM 那樣。這種思路雖然理論上是可行的,但是筆者沒有見過有人這麼做,頁面一多起來,100多個頁面,上千個 View,都在一個 VC 上控制,這樣開發有點蛋疼。

兩者解決的需求也存在差異

iOS 的組件化一部分也是解決了代碼復用性的問題,但是更多的是解決耦合性大,開發效率合作性低的問題。而 Vue 的組件化更多的是為了解決代碼復用性的問題。

兩者的組件化的方向也有不同。

iOS 平台由於有 UIKit 這類蘋果已經封裝好的 Framework,所以基礎控制項已經封裝完成,不需要我們自己手動封裝了,所以 iOS 的組件著眼於一個大的功能,比如網路庫,購物車,我的錢包,整個業務塊。前端的頁面布局是在 DOM 上進行的,只有最基礎的 CSS 的標籤,所以控制項都需要自己寫,Vue 的組件化封裝的可復用的單文件組件其實更加類似於 iOS 這邊的 ViewModel。

所以從封裝性上來講,兩者可以相互借鑒的地方並不多。iOS 能從前端借鑒的東西在狀態管理這一塊,單向數據流的思想。不過這一塊思想雖然好,但是如何能在自家公司的app上得到比較好的實踐,依舊是仁者見仁智者見智的事了,並不是所有的業務都適合單向數據流。


Reference:

Vue.js 官方文檔

GitHub Repo:Halfrost-Field

Follow: halfrost · GitHub

Source: halfrost.com/vue_ios_mo

推薦閱讀:

React解決了前端開發中的哪些痛點?
框架、模式、組件三者的區別是什麼?

TAG:Vuejs | iOS | 组件 |