序
學(xué)如逆水行舟,不進(jìn)則退。共勉!
今天一起來(lái)學(xué)習(xí)一篇文章吧,大話大前端時(shí)代--Vue與iOS的組件化。一篇很老的文章,但是收獲還是挺多的,特別是對(duì)接觸前端沒(méi)多久的同學(xué)們。也可在評(píng)論區(qū)相互討論借鑒學(xué)習(xí),不要吝嗇你的贊。
Vue篇
一. 組件化的需求
為了提高代碼復(fù)用性,減少重復(fù)性的開(kāi)發(fā),我們就把相關(guān)的代碼按照 template、style、script 拆分,封裝成一個(gè)個(gè)的組件。組件可以擴(kuò)展
HTML 元素,封裝可重用的 HTML 代碼,我們可以將組件看作自定義的 HTML 元素。在 Vue 里面,每個(gè)封裝好的組件可以看成一個(gè)個(gè)的 ViewModel。
二. 如何封裝組件
談到如何封裝的問(wèn)題,就要先說(shuō)說(shuō)怎么去組織組件的問(wèn)題。
如果在簡(jiǎn)單的 SPA 項(xiàng)目中,可以直接用 Vue.component 去定義一個(gè)全局組件,項(xiàng)目一旦復(fù)雜以后,就會(huì)出現(xiàn)弊端了:
- 全局定義(Global definitions) 強(qiáng)制要求每個(gè) component 中的命名不得重復(fù)
- 字符串模板(String templates) 缺乏語(yǔ)法高亮,在 HTML 有多行的時(shí)候,需要用到丑陋的 \
- 不支持 CSS(No CSS support) 意味著當(dāng) HTML 和 JavaScript 組件化時(shí),CSS 明顯被遺漏
- 沒(méi)有構(gòu)建步驟(No build step) 限制只能使用 HTML 和 ES5 JavaScript, 而不能使用預(yù)處理器,如 Pug (formerly Jade) 和 Babel
而且現(xiàn)在公司級(jí)的項(xiàng)目,大多數(shù)都會(huì)引入工程化的管理,用包管理工具去管理,npm 或者 yarn。所以 Vue 在復(fù)雜的項(xiàng)目中用 Vue.component 去定義一個(gè)組件的方式就不適合了。這里就需要用到單文件組件,還可以使用 Webpack 或 Browserify 等構(gòu)建工具。比如下面這個(gè)Hello.vue組件,整個(gè)文件就是一個(gè)組件。

在單文件組件中,整個(gè)文件都是一個(gè) CommonJS 模塊,里面包含了組件對(duì)應(yīng)的 HTML、組件內(nèi)的處理邏輯 Javascript、組件的樣式 CSS。
在組件的 script 標(biāo)簽中,需要封裝該組件 ViewModel 的行為。
- data
組件的初始化數(shù)據(jù),以及私有屬性。 - props
組件的屬性,這里的屬性專(zhuān)門(mén)用來(lái)接收父子組件通信的數(shù)據(jù)。(這里可以類(lèi)比 iOS 里面的 @property ) - methods
組件內(nèi)的處理邏輯函數(shù)。 - watch
需要額外監(jiān)聽(tīng)的屬性(這里可以類(lèi)比 iOS 里面的 KVO ) - computed
組件的計(jì)算屬性 - components
所用到的子組件 - lifecycle hooks
生命周期的鉤子函數(shù)。一個(gè)組件也是有生命周期的,有如下這些:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、activated、deactivated、beforeDestroy、destroyed等生命周期。在這些鉤子函數(shù)里面可以加上我們預(yù)設(shè)的處理邏輯。(這里可以類(lèi)比 iOS 里面的 ViewController 的生命周期 )

如此看來(lái),在 Vue 里面封裝一個(gè)單文件組件,和在 iOS 里面封裝一個(gè) ViewModel 的思路是完全一致的。接下來(lái)的討論無(wú)特殊說(shuō)明,針對(duì)的都是單文件組件。
三. 如何劃分組件
一般劃分組件分可以按照以下標(biāo)準(zhǔn)去劃分:
- 頁(yè)面區(qū)域:
header、footer、sidebar…… - 功能模塊:
select、pagination……
這里舉個(gè)例子來(lái)說(shuō)明一起前端是如何劃分組件的。
1. 頁(yè)面區(qū)域
還是以 objc中國(guó) 的首頁(yè)頁(yè)面為例

我們可以把上面的頁(yè)面按照布局,先抽象圖片中間的樣子,然后接著按照頁(yè)面的區(qū)域劃分組件,最后可以得到最右邊的組件樹(shù)。
在 Vue 實(shí)例的根組件,加載 layout。

根據(jù)抽象出來(lái)的組件樹(shù),可以進(jìn)一步的向下細(xì)分各個(gè)小組件。

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

上圖就是我們的 layout.vue 的全部實(shí)現(xiàn)。在這個(gè)單文件組件中里面引用了三個(gè)子組件,navigationBar、footerView、content。由于 content 里面是又各個(gè)路由頁(yè)面組成,所以這里聲明成 router-view。
至于各個(gè)子組件的具體實(shí)現(xiàn)這里就不在贅述了。
2. 功能模塊
一般項(xiàng)目里面詳情頁(yè)的內(nèi)容最多,我們就以以 objc中國(guó) 的詳情頁(yè)面為例

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

從上往下依次展開(kāi),見(jiàn)上圖。

經(jīng)過(guò)功能上的劃分以后,整個(gè)詳情頁(yè)面的代碼變的異常清爽,整個(gè)頁(yè)面就是6個(gè)單文件的子組件,每個(gè)子組件的邏輯封裝在各自的組件里面,詳情頁(yè)面就是把他們都組裝在了一起,代碼可讀性高,后期維護(hù)也非常方便。
綜上可以看出,前端 SPA 頁(yè)面抽象出來(lái)就是一個(gè)大的組件樹(shù)。
四. 組件化原理
舉個(gè)例子:

在上面的例子中,在 <parent-component> 父組件里面聲明了一個(gè) <child-component>,最終渲染出來(lái)的結(jié)果是:


上述代碼的執(zhí)行順序如下:
- 子組件先在父組件中的 components 中進(jìn)行注冊(cè)。
- 父組件利用 Vue.component 注冊(cè)到全局。
- 當(dāng)渲染父組件的時(shí)候,渲染到
<child-component>,會(huì)把子組件也渲染出來(lái)。
值得說(shuō)明的一點(diǎn)是,Vue 進(jìn)行模板解析的時(shí)候會(huì)遵循以下 html 常見(jiàn)的限制:
- a 不能包含其它的交互元素(如按鈕,鏈接)
- ul 和 ol 只能直接包含 li
- select 只能包含 option 和 optgroup
- table 只能直接包含 thead, tbody, tfoot, tr, caption, col, colgroup
- tr 只能直接包含 th 和 td
五. 組件分類(lèi)
組件的種類(lèi)可分為以下4種:
- 普通組件
- 動(dòng)態(tài)組件
- 異步組件
- 遞歸組件
1. 普通組件
之前講的都是普通的組件,這里就不在贅述了。
2. 動(dòng)態(tài)組件
動(dòng)態(tài)組件利用的是 is 的特性,可以設(shè)置多個(gè)組件可以使用同一個(gè)掛載點(diǎn),并動(dòng)態(tài)切換。

現(xiàn)在 <component> 組件的具體類(lèi)型用 currentView 來(lái)表示了,我們就可以通過(guò)更改 currentView 的值,來(lái)動(dòng)態(tài)加載各個(gè)組件。上述例子中,可以不斷的更改 data 里面的 currentView ,來(lái)達(dá)到動(dòng)態(tài)加載 home、posts、archive 三個(gè)不同組件的目的。
3. 異步組件
Vue允許將組件定義為一個(gè)工廠函數(shù),在組件需要渲染時(shí)觸發(fā)工廠函數(shù)動(dòng)態(tài)地解析組件,并且將結(jié)果緩存起來(lái):

動(dòng)態(tài)組件可配合 webpack 實(shí)現(xiàn)代碼分割,webpack 可以將代碼分割成塊,在需要此塊時(shí)再使用 ajax 的方式下載:

4. 遞歸組件
如果一個(gè)組件設(shè)置了 name 屬性,那么它就可以變成遞歸組件了。
遞歸組件可以利用模板里面的 name 不斷的遞歸調(diào)用自己。

上面這段代碼是一個(gè)錯(cuò)誤代碼,這樣寫(xiě)模板的話就會(huì)導(dǎo)致遞歸死循環(huán),最終報(bào)錯(cuò) “max stack size exceeded”。解決辦法需要打破死循環(huán),比如 v-if 返回 false。
六. 組件間的消息傳遞和狀態(tài)管理
在 Vue 中,組件消息傳遞的方式主要分為3種:
- 父子組件之間的消息傳遞
- Event Bus
- Vuex 單向數(shù)據(jù)流
1. 父子組件之間的消息傳遞

父子組件的傳遞方式比較單一,在 Vue 2.0 以后,父子組件的關(guān)系可以總結(jié)為 ** props down, events up **。父組件通過(guò) props 向下傳遞數(shù)據(jù)給子組件,子組件通過(guò) events 給父組件發(fā)送消息。
父向子傳遞
舉個(gè)例子:

在 child 組件的 props 中聲明了一個(gè) msg 屬性,在父組件中利用這個(gè)屬性把值傳給子組件。
這里有一點(diǎn)需要注意的是,在非字符串模板中, camelCased (駝峰式) 命名的 prop 需要轉(zhuǎn)換為相對(duì)應(yīng)的 kebab-case (短橫線隔開(kāi)式) 命名。
上面這個(gè)例子是靜態(tài)的綁定,Vue 也支持動(dòng)態(tài)綁定,這里也支持 v-bind 指令進(jìn)行動(dòng)態(tài)的綁定 props 。
父向子傳遞是一個(gè)單向數(shù)據(jù)流的過(guò)程,prop 是單向綁定的:當(dāng)父組件的屬性變化時(shí),將傳導(dǎo)給子組件,但是不會(huì)反過(guò)來(lái)。這是為了防止子組件無(wú)意修改了父組件的狀態(tài)——這會(huì)讓?xiě)?yīng)用的數(shù)據(jù)流難以理解。
另外,每次父組件更新時(shí),子組件的所有 prop 都會(huì)更新為最新值。這意味著你不應(yīng)該在子組件內(nèi)部改變 prop。Vue 建議子組件的 props 是 immutable 的。
這里就會(huì)牽涉到2類(lèi)問(wèn)題:
- 由于單向數(shù)據(jù)流的原因,會(huì)導(dǎo)致子組件的數(shù)據(jù)或者狀態(tài)和父組件的不一致,為了同步,在子組件里面反數(shù)據(jù)流的去修改父組件的數(shù)據(jù)或者數(shù)據(jù)。
- 子組件接收到了 props 的值以后,有2種原因想要改變它,第一種原因是,prop 作為初始值傳入后,子組件想把它當(dāng)作局部數(shù)據(jù)來(lái)用;第二種原因是,prop 作為初始值傳入,由子組件處理成其它數(shù)據(jù)輸出。
這兩類(lèi)問(wèn)題,開(kāi)發(fā)者強(qiáng)行更改,也都是可以實(shí)現(xiàn)的,但是會(huì)導(dǎo)致不令人滿意的 “后果” 。第一個(gè)問(wèn)題強(qiáng)行手動(dòng)修改父組件的數(shù)據(jù)或者狀態(tài)以后,導(dǎo)致數(shù)據(jù)流混亂不堪。只看父組件,很難理解父組件的狀態(tài)。因?yàn)樗赡鼙蝗我庾咏M件修改!理想情況下,只有組件自己能修改它的狀態(tài)。第二個(gè)問(wèn)題強(qiáng)行手動(dòng)修改子組件的 props 以后,Vue 會(huì)在控制臺(tái)給出警告。
如果優(yōu)雅的解決這2種問(wèn)題呢?一個(gè)個(gè)的來(lái)說(shuō):
(1)第一個(gè)問(wèn)題,換成雙向綁定就可以解決。
在 Vue 2.3.0+ 以后的版本,雙向綁定有2種方式
第一種方式:
利用 .sync 修飾符,在 Vue 2.3.0+ 以后作為一個(gè)編譯時(shí)的語(yǔ)法糖存在。它會(huì)被擴(kuò)展為一個(gè)自動(dòng)更新父組件屬性的 v-on 偵聽(tīng)器。

第二種方式:
自定義事件可以用來(lái)創(chuàng)建自定義的表單輸入組件,使用 v-model 來(lái)進(jìn)行數(shù)據(jù)雙向綁定。

在這種方式下進(jìn)行的雙向綁定必須滿足2個(gè)條件:
- 接受一個(gè) value 屬性
- 在有新的值時(shí)觸發(fā) input 事件
官方推薦的2種雙向綁定的方式就是上述2種方法。不過(guò)還有一些隱性的雙向綁定,可能無(wú)意間就會(huì)造成bug的產(chǎn)生。
pros 是單向數(shù)據(jù)傳遞,父組件把數(shù)據(jù)傳遞給子組件,需要尤其注意的是,傳遞的數(shù)據(jù)如果是引用類(lèi)型(比如數(shù)組和對(duì)象),那么默認(rèn)就是雙向數(shù)據(jù)綁定,子組件的更改都會(huì)影響到父組件里面。在這種情況下,如果人為不知情,就會(huì)出現(xiàn)一些莫名其妙的bug,所以需要注意引用類(lèi)型的數(shù)據(jù)傳遞。
(2)第二個(gè)問(wèn)題,有兩種做法:
- 第一種做法是:定義一個(gè)局部變量,并用 prop 的值初始化它:

- 第二種做法是:定義一個(gè)計(jì)算屬性,處理 prop 的值并返回。

父向子傳遞還可以傳遞模板,使用 slot 分發(fā)內(nèi)容。
slot 是 Vue 的一個(gè)內(nèi)置的自定義元素指令。slot 在 bind 回調(diào)函數(shù)中,根據(jù) name 獲取將要替換插槽的元素,如果上下文環(huán)境中有所需替換的內(nèi)容,則調(diào)用父元素的 replaceChild 方法,用替換元素講 slot 元素替換;否則直接刪除將要替換的元素。如果替換插槽元素中有一個(gè)頂級(jí)元素,且頂級(jí)元素的第一子節(jié)點(diǎn)為 DOM 元素,且該節(jié)點(diǎn)有 v-if 指令,且 slot 元素中有內(nèi)容,則替換模板將增加 v-else 模板放入插槽中的內(nèi)容。如果 v-if 指令為 false,則渲染 else 模板內(nèi)容。
子向父?jìng)鬟f
子組件要把數(shù)據(jù)傳遞回父組件,方式很單一,那利用自定義事件!
父組件使用 emit(eventName) 觸發(fā)事件
舉個(gè)簡(jiǎn)單的例子:

這里也可以通過(guò)父子之間的關(guān)系進(jìn)行傳遞數(shù)據(jù)(直接修改數(shù)據(jù)),但是不推薦這種方法,例如 this.children 直接調(diào)用父或者子組件的方法,這里類(lèi)比iOS里面的ViewControllers方法,在這個(gè)數(shù)組里面可以直接拿到所有 VC ,然后就可以調(diào)用他們暴露在.h里面的方法了。但是這種方式相互直接耦合性太大了。
2. Event Bus
Event Bus 這個(gè)概念對(duì)移動(dòng)端的同學(xué)來(lái)說(shuō)也比較熟悉,因?yàn)樵诎沧块_(kāi)發(fā)中就有這個(gè)概念。在 iOS 開(kāi)發(fā)中,可以類(lèi)比消息總線。具體實(shí)現(xiàn)可以是通知 Notification 或者 ReactiveCocoa 中的信號(hào)傳遞。
Event Bus 的實(shí)現(xiàn)還是借助 Vue 的實(shí)例。新建一個(gè)新的 Vue,專(zhuān)門(mén)用來(lái)做消息總線。


3. Vuex 單向數(shù)據(jù)流
由于本篇文章重點(diǎn)討論組件化的問(wèn)題,所以這里 Vuex 只是說(shuō)明用法,至于原理的東西之后會(huì)單獨(dú)開(kāi)一篇文章來(lái)分析。

這一張圖就描述了 Vuex 是什么。Vuex 專(zhuān)為 Vue.js 應(yīng)用程序開(kāi)發(fā)的狀態(tài)管理模式。它采用集中式存儲(chǔ)管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測(cè)的方式發(fā)生變化。
上圖中箭頭的指向就描述了數(shù)據(jù)的流向。數(shù)據(jù)的流向是單向的,從 Actions 流向 State,State 中的數(shù)據(jù)改變了從而影響到 View 展示數(shù)據(jù)的變化。

從簡(jiǎn)單的 Actions、State、View 三個(gè)角色,到現(xiàn)在增加了一個(gè) Mutations。Mutations 現(xiàn)在變成了更改 Vuex 的 store 中的狀態(tài)的唯一方法是提交 mutation。Vuex 中的 mutations 非常類(lèi)似于事件:每個(gè) mutation 都有一個(gè)字符串的 事件類(lèi)型 (type) 和 一個(gè) 回調(diào)函數(shù) (handler)。
一般在組件中進(jìn)行 commit 調(diào)用 Mutation 方法

Actions 和 Mutations 的區(qū)別在于:
- Action 提交的是 mutation,而不是直接變更狀態(tài)。
- Action 可以包含任意異步操作,而 Mutations 必須是同步函數(shù)。
一般在組件中進(jìn)行 dispatch 調(diào)用 Actions 方法

Vuex 官方針對(duì) Vuex 的最佳實(shí)踐,給出了一個(gè)項(xiàng)目模板結(jié)構(gòu),希望大家都能按照這種模式去組織我們的項(xiàng)目。

關(guān)于這個(gè)例子的詳細(xì)代碼在這里
七. 組件注冊(cè)方式
組件的注冊(cè)方式主要就分為2種:全局注冊(cè)和局部注冊(cè)
1. 全局注冊(cè)
利用 Vue.component 指令進(jìn)行全局注冊(cè)

注冊(cè)完的組件就可以在父實(shí)例中以自定義元素 <my-component></my-component> 的形式使用。

2. 局部注冊(cè)
全局注冊(cè)組件會(huì)拖慢一些頁(yè)面的加載速度,有些組件只需要用的到時(shí)候再加載,所以不必在全局注冊(cè)每個(gè)組件。于是就有了局部注冊(cè)的方式。

iOS 篇

一. 組件化的需求
在 iOS Native app 前期開(kāi)發(fā)的時(shí)候,如果參與的開(kāi)發(fā)人員也不多,那么代碼大多數(shù)都是寫(xiě)在一個(gè)工程里面的,這個(gè)時(shí)候業(yè)務(wù)發(fā)展也不是太快,所以很多時(shí)候也能保證開(kāi)發(fā)效率。
但是一旦項(xiàng)目工程龐大以后,開(kāi)發(fā)人員也會(huì)逐漸多起來(lái),業(yè)務(wù)發(fā)展突飛猛進(jìn),這個(gè)時(shí)候單一的工程開(kāi)發(fā)模式就會(huì)暴露出弊端了。
- 項(xiàng)目?jī)?nèi)代碼文件耦合比較嚴(yán)重
- 容易出現(xiàn)沖突,大公司同時(shí)開(kāi)發(fā)一個(gè)項(xiàng)目的人多,每次 pull 一下最新代碼就會(huì)有很多沖突,有時(shí)候合并代碼需要半個(gè)小時(shí)左右,這會(huì)耽誤開(kāi)發(fā)效率。
- 業(yè)務(wù)方的開(kāi)發(fā)效率不夠高,開(kāi)發(fā)人員一多,每個(gè)人都只想關(guān)心自己的組件,但是卻要編譯整個(gè)項(xiàng)目,與其他不相干的代碼糅合在一起。調(diào)試起來(lái)也不方便,即使開(kāi)發(fā)一個(gè)很小的功能,都要去把整個(gè)項(xiàng)目都編譯一遍,調(diào)試效率低。
為了解決這些問(wèn)題,iOS 項(xiàng)目就出現(xiàn)了組件化的概念。所以 iOS 的組件化是為了解決上述這些問(wèn)題的,這里與前端組件化解決的痛點(diǎn)不同。
iOS 組件化以后能帶來(lái)如下的好處:
- 加快編譯速度(不用編譯主客那一大坨代碼了,各個(gè)組件都是靜態(tài)庫(kù))
- 自由選擇開(kāi)發(fā)姿勢(shì)(MVC / MVVM / FRP)
- 方便 QA 有針對(duì)性地測(cè)試
- 提高業(yè)務(wù)開(kāi)發(fā)效率
iOS 組件化的封裝性只是其中的一小部分,更加關(guān)心的是如何拆分組件,如何解除耦合。前端的組件化可能會(huì)更加注重組件的封裝性,高可復(fù)用性。
二. 如何封裝組件
iOS 的組件化手段非常單一,就是利用 Cocoapods 封裝成 pod 庫(kù),主工程分別引用這些 pod 即可。越來(lái)越多的第三方庫(kù)也都在 Cocoapods 上發(fā)布自己的最新版本,大公司也在公司內(nèi)部維護(hù)了公司私有的 Cocoapods 倉(cāng)庫(kù)。一個(gè)封裝完美的 Pod 組件,主工程使用起來(lái)非常方便。

最終想要達(dá)到的理想目標(biāo)就是主工程就是一個(gè)殼工程,其他所有代碼都在組件 Pods 里面,主工程的工作就是初始化,加載這些組件的,沒(méi)有其他任何代碼了。
三. 如何劃分組件
iOS 劃分組件雖然沒(méi)有一個(gè)很明確的標(biāo)準(zhǔn),因?yàn)槊總€(gè)項(xiàng)目都不同,劃分組件的粗粒度也不同,但是依舊有一個(gè)劃分的原則。
App之間可以重用的 Util、Category、網(wǎng)絡(luò)層和本地存儲(chǔ) storage 等等這些東西抽成了 Pod 庫(kù)。還有些一些和業(yè)務(wù)相關(guān)的,也是在各個(gè)App之間重用的。
原則就是:要在App之間共享的代碼就應(yīng)該抽成 Pod 庫(kù),把它們作為一個(gè)個(gè)組件。不在 App 間共享的業(yè)務(wù)線,也應(yīng)該抽成 Pod,解除它與工程其他的文件耦合性。
常見(jiàn)的劃分方法都是從底層開(kāi)始動(dòng)手,網(wǎng)絡(luò)庫(kù),路由,MVVM框架,數(shù)據(jù)庫(kù)存儲(chǔ),加密解密,工具類(lèi),地圖,基礎(chǔ)SDK,APM,風(fēng)控,埋點(diǎn)……從下往上,到了上層就是各個(gè)業(yè)務(wù)方的組件了,最常見(jiàn)的就類(lèi)似于購(gòu)物車(chē),我的錢(qián)包,登錄,注冊(cè)等。
四. 組件化原理
iOS 的組件化是借助 Cocoapods 完成的。。
這里簡(jiǎn)單的分析一下 pod 進(jìn)來(lái)的庫(kù)是什么加載到主工程的。
pod 會(huì)依據(jù) Podfile 文件里面的依賴庫(kù),把這些庫(kù)的源代碼下載下來(lái),并創(chuàng)建好 Pods workspace。當(dāng)程序編譯的時(shí)候,會(huì)預(yù)先執(zhí)行2個(gè) pod 設(shè)置進(jìn)來(lái)的腳本。

在上面這個(gè)腳本中,會(huì)把 Pods 里面的打包好的靜態(tài)庫(kù)合并到 libPods-XXX.a 這個(gè)靜態(tài)庫(kù)里面,這個(gè)庫(kù)是主工程依賴的庫(kù)。

Pods 另外一個(gè)腳本是加載資源的。見(jiàn)下圖。

這里加載的資源是 Pods 庫(kù)里面的一些圖片資源,或者是 Boudle 里面的 xib ,storyboard,音樂(lè)資源等等。這些資源也會(huì)一起打到 libPods-XXX.a 這個(gè)靜態(tài)庫(kù)里面。

上圖就是加載資源的腳本。
五. 組件分類(lèi)
iOS 的組件主要分為2種形式:
- 靜態(tài)庫(kù)
- 動(dòng)態(tài)庫(kù)
靜態(tài)庫(kù)一般是以 .a 和 .framework 結(jié)尾的文件,動(dòng)態(tài)庫(kù)一般是以 .dylib 和 .framework 結(jié)尾的文件。
這里可以看到,一個(gè) .framework 結(jié)尾的文件僅僅通過(guò)文件類(lèi)型是無(wú)法判斷出它是一個(gè)靜態(tài)庫(kù)還是一個(gè)動(dòng)態(tài)庫(kù)。
靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)的區(qū)別在于:
- .a文件肯定是靜態(tài)庫(kù),.dylib肯定是動(dòng)態(tài)庫(kù),.framework可能是靜態(tài)庫(kù)也可能是動(dòng)態(tài)庫(kù);
- 靜態(tài)庫(kù)在鏈接其他庫(kù)的情況時(shí),它會(huì)被完整的復(fù)制到可執(zhí)行文件中,如果多個(gè)App都使用了同一個(gè)靜態(tài)庫(kù),那么每個(gè)App都會(huì)拷貝一份,缺點(diǎn)是浪費(fèi)內(nèi)存。類(lèi)似于定義一個(gè)基本變量,使用該基本變量是是新復(fù)制了一份數(shù)據(jù),而不是原來(lái)定義的;靜態(tài)庫(kù)的好處很明顯,編譯完成之后,庫(kù)文件實(shí)際上就沒(méi)有作用了。目標(biāo)程序沒(méi)有外部依賴,直接就可以運(yùn)行。當(dāng)然其缺點(diǎn)也很明顯,就是會(huì)使用目標(biāo)程序的體積增大。
- 動(dòng)態(tài)庫(kù)不會(huì)被復(fù)制,只有一份,程序運(yùn)行時(shí)動(dòng)態(tài)加載到內(nèi)存中,系統(tǒng)只會(huì)加載一次,多個(gè)程序共用一份,節(jié)約了內(nèi)存。而且使用動(dòng)態(tài)庫(kù),可以不重新編譯連接可執(zhí)行程序的前提下,更新動(dòng)態(tài)庫(kù)文件達(dá)到更新應(yīng)用程序的目的。
六. 組件間的消息傳遞和狀態(tài)管理
之前我們討論過(guò)了,iOS 組件化十分關(guān)注解耦性,這算是組件化的一個(gè)重要目的。iOS 各個(gè)組件之間消息傳遞是用路由來(lái)實(shí)現(xiàn)的
七. 組件注冊(cè)方式
iOS 組件注冊(cè)的方式主要有3種:
- load方法注冊(cè)
- 讀取 plist 文件注冊(cè)
- Annotation注解方式注冊(cè)
前兩種方式都比較簡(jiǎn)單,容易理解。
第一種方式在 load 方法里面利用 Runtime 把組件名和組件實(shí)例的映射關(guān)系保存到一個(gè)全局的字典里,方便程序啟動(dòng)以后可以隨時(shí)調(diào)用。
第二種方式是把組件名和組件實(shí)例的映射關(guān)系預(yù)先寫(xiě)在 plist 文件中。程序需要的時(shí)候直接去讀取這個(gè) plist 文件。plist 文件可以從服務(wù)器讀取過(guò)來(lái),這樣 App 還能有一定的動(dòng)態(tài)性。
第三種方式比較黑科技。利用的是 Mach-o 的數(shù)據(jù)結(jié)構(gòu),在程序編程鏈接成可執(zhí)行文件的時(shí)候,就把相關(guān)注冊(cè)信息直接寫(xiě)入到最終的可執(zhí)行文件的 Data 數(shù)據(jù)段內(nèi)。程序執(zhí)行以后,直接去那個(gè)段內(nèi)去讀取想要的數(shù)據(jù)即可。
總結(jié)

經(jīng)過(guò)上面的分析,我們可以看出 Vue 的組件化和 iOS 的組件化區(qū)別還是比較大的。
兩者平臺(tái)上開(kāi)發(fā)方式存在差異
主要體現(xiàn)在單頁(yè)應(yīng)用和類(lèi)多頁(yè)應(yīng)用的差異。
現(xiàn)在前端比較火的一種應(yīng)用就是單頁(yè)Web應(yīng)用(single page web application,SPA),顧名思義,就是只有一張Web頁(yè)面的應(yīng)用,是加載單個(gè)HTML 頁(yè)面并在用戶與應(yīng)用程序交互時(shí)動(dòng)態(tài)更新該頁(yè)面的Web應(yīng)用程序。
瀏覽器從服務(wù)器加載初始頁(yè)面,以及整個(gè)應(yīng)用所需的腳本(框架、庫(kù)、應(yīng)用代碼)和樣式表。當(dāng)用戶定位到其他頁(yè)面時(shí),不會(huì)觸發(fā)頁(yè)面刷新。通過(guò) HTML5 History API 更新頁(yè)面的 URL 。瀏覽器通過(guò) AJAX 請(qǐng)求檢索新頁(yè)面(通常以 JSON 格式)所需的新數(shù)據(jù)。然后, SPA 通過(guò) JavaScript 動(dòng)態(tài)更新已經(jīng)在初始頁(yè)面加載中已經(jīng)下載好的新頁(yè)面。這種模式類(lèi)似于原生手機(jī)應(yīng)用的工作原理。
但是 iOS 開(kāi)發(fā)更像類(lèi) MPA (Multi-Page Application)。

往往一個(gè)原生的 App ,頁(yè)面差不多應(yīng)該是上圖這樣。當(dāng)然,可能有人會(huì)說(shuō),依舊可以把這么多頁(yè)面寫(xiě)成一個(gè)頁(yè)面,在一個(gè) VC 里面控制所有的 View,就像前端的 DOM 那樣。這種思路雖然理論上是可行的,但是筆者沒(méi)有見(jiàn)過(guò)有人這么做,頁(yè)面一多起來(lái),100多個(gè)頁(yè)面,上千個(gè) View,都在一個(gè) VC 上控制,這樣開(kāi)發(fā)有點(diǎn)蛋疼。
兩者解決的需求也存在差異
iOS 的組件化一部分也是解決了代碼復(fù)用性的問(wèn)題,但是更多的是解決耦合性大,開(kāi)發(fā)效率合作性低的問(wèn)題。而 Vue 的組件化更多的是為了解決代碼復(fù)用性的問(wèn)題。
兩者的組件化的方向也有不同。
iOS 平臺(tái)由于有 UIKit 這類(lèi)蘋(píng)果已經(jīng)封裝好的 Framework,所以基礎(chǔ)控件已經(jīng)封裝完成,不需要我們自己手動(dòng)封裝了,所以 iOS 的組件著眼于一個(gè)大的功能,比如網(wǎng)絡(luò)庫(kù),購(gòu)物車(chē),我的錢(qián)包,整個(gè)業(yè)務(wù)塊。前端的頁(yè)面布局是在 DOM 上進(jìn)行的,只有最基礎(chǔ)的 CSS 的標(biāo)簽,所以控件都需要自己寫(xiě),Vue 的組件化封裝的可復(fù)用的單文件組件其實(shí)更加類(lèi)似于 iOS 這邊的 ViewModel。
所以從封裝性上來(lái)講,兩者可以相互借鑒的地方并不多。iOS 能從前端借鑒的東西在狀態(tài)管理這一塊,單向數(shù)據(jù)流的思想。不過(guò)這一塊思想雖然好,但是如何能在自家公司的app上得到比較好的實(shí)踐,依舊是仁者見(jiàn)仁智者見(jiàn)智的事了,并不是所有的業(yè)務(wù)都適合單向數(shù)據(jù)流。
更多資料請(qǐng)關(guān)注個(gè)人資料。