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

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

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


Vue 篇

一. 組件化的需求

爲了提升代碼複用性,減小重複性的開發,咱們就把相關的代碼按照 template、style、script 拆分,封裝成一個個的組件。組件能夠擴展
HTML 元素,封裝可重用的 HTML 代碼,咱們能夠將組件看做自定義的 HTML 元素。在 Vue 裏面,每一個封裝好的組件能夠當作一個個的 ViewModel。前端

二. 如何封裝組件

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

若是在簡單的 SPA 項目中,能夠直接用 Vue.component 去定義一個全局組件,項目一旦複雜之後,就會出現弊端了:java

  1. 全局定義(Global definitions) 強制要求每一個 component 中的命名不得重複webpack

  2. 字符串模板(String templates) 缺少語法高亮,在 HTML 有多行的時候,須要用到醜陋的 \ios

  3. 不支持 CSS(No CSS support) 意味着當 HTML 和 JavaScript 組件化時,CSS 明顯被遺漏git

  4. 沒有構建步驟(No build step) 限制只能使用 HTML 和 ES5 JavaScript, 而不能使用預處理器,如 Pug (formerly Jade) 和 Babelgithub

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

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

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

  • data
    組件的初始化數據,以及私有屬性。

  • props
    組件的屬性,這裏的屬性專門用來接收父子組件通訊的數據。(這裏能夠類比 iOS 裏面的 @property )

  • methods
    組件內的處理邏輯函數。

  • watch
    須要額外監聽的屬性(這裏能夠類比 iOS 裏面的 KVO )

  • computed
    組件的計算屬性

  • components
    所用到的子組件

  • lifecycle hooks
    生命週期的鉤子函數。一個組件也是有生命週期的,有以下這些:beforeCreatecreatedbeforeMountmountedbeforeUpdateupdatedactivateddeactivatedbeforeDestroydestroyed等生命週期。在這些鉤子函數裏面能夠加上咱們預設的處理邏輯。(這裏能夠類比 iOS 裏面的 ViewController 的生命週期 )

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

三. 如何劃分組件

通常劃分組件分能夠按照如下標準去劃分:

  1. 頁面區域:

    header、footer、sidebar……
  2. 功能模塊:

    select、pagenation……

這裏舉個例子來講明一塊兒前端是如何劃分組件的。

1. 頁面區域

仍是以 objc中國 的首頁頁面爲例

咱們能夠把上面的頁面按照佈局,先抽象圖片中間的樣子,而後接着按照頁面的區域劃分組件,最後能夠獲得最右邊的組件樹。

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

import Vue from 'vue';
import store from './store';
import router from './router';
import Layout from './components/layout';

new Vue({
  el: '#app',
  router,
  store,
  template: '<Layout/>',
  components: {
    Layout
  }
});

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

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

上圖就是咱們的 layout.vue 的所有實現。在這個單文件組件中裏面引用了三個子組件,navigationBar、footerView、content。因爲 content 裏面是又各個路由頁面組成,因此這裏聲明成 router-view。

至於各個子組件的具體實現這裏就不在贅述了,具體代碼能夠看這裏navigationBar.vuefooterViewlayout.vue

2. 功能模塊

通常項目裏面詳情頁的內容最多,咱們就以以 objc中國 的詳情頁面爲例

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

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

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

詳情頁面具體的代碼在這裏https://github.com/halfrost/vue-objccn/blob/master/src/pages/productsDetailInfo.vue

6個子組件的代碼在這裏https://github.com/halfrost/vue-objccn/tree/master/src/components/productsDetailInfo,具體的代碼見連接,這裏就不在贅述了。

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

四. 組件化原理

舉個例子:

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

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

This is a Parent component !
This is a child component !

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

  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({
  el: '#example',
  data: {
    currentView: 'home'
  },
  components: {
    home: { /* ... */ },
    posts: { /* ... */ },
    archive: { /* ... */ }
  }
})


<component v-bind:is="currentView">
  <!-- 組件在 vm.currentview 變化時改變! -->
</component>

如今 <component> 組件的具體類型用 currentView 來表示了,咱們就能夠經過更改 currentView 的值,來動態加載各個組件。上述例子中,能夠不斷的更改 data 裏面的 currentView ,來達到動態加載 home、posts、archive 三個不一樣組件的目的。

3. 異步組件

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

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

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

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

4. 遞歸組件

若是一個組件設置了 name 屬性,那麼它就能夠變成遞歸組件了。

遞歸組件能夠利用模板裏面的 name 不斷的遞歸調用本身。

name: 'recursion-component',
template: '<div><recursion-component></recursion-component></div>'

上面這段代碼是一個錯誤代碼,這樣寫模板的話就會致使遞歸死循環,最終報錯 「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', {
  // 聲明 props
  props: ['msg'],
  // prop 能夠用在模板內
  // 能夠用 `this.msg` 設置
  template: '<span>{{ msg }}</span>'
})

<child msg="hello!"></child>

在 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 偵聽器。

// 聲明一個雙向綁定
<comp :foo.sync="bar"></comp>


// 上面一行代碼會被會被擴展爲下面這一行:
<comp :foo="bar" @update:foo="val => bar = val"></comp>

// 當子組件須要更新 foo 的值時,它會顯式地觸發一個更新事件:
this.$emit('update:foo', newValue)

第二種方式:

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

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

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

  • 接受一個 value 屬性

  • 在有新的值時觸發 input 事件

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

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

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

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

props: ['initialCounter'],
data: function () {
  return { counter: this.initialCounter }
}
  • 第二種作法是:定義一個計算屬性,處理 prop 的值並返回。

props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

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

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

子向父傳遞

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

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

舉個簡單的例子:

// 在子組件裏面有一個 button
<button @click="emitMyEvent">emit</button>

emitMyEvent() {
  this.$emit('my-event', this.hello);
}


// 在父組件裏面監聽子組件的自定義事件
<child @my-event="getMyEvent"></child>

getMyEvent() {
    console.log(' i got child event ');
}

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

2. Event Bus

Event Bus 這個概念對移動端的同窗來講也比較熟悉,由於在安卓開發中就有這個概念。在 iOS 開發中,能夠類比消息總線。具體實現能夠是通知 Notification 或者 ReactiveCocoa 中的信號傳遞。

Event Bus 的實現仍是藉助 Vue 的實例。新建一個新的 Vue,專門用來作消息總線。

var eventBus = new Vue()

// 在 A 組件中引入 eventBus
eventBus.$emit('myEvent', 1)

// 在要監聽的組件中監聽
eventBus.$on('id-selected', () => {
  // ...
})

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);

Actions 和 Mutations 的區別在於:

  • Action 提交的是 mutation,而不是直接變動狀態。

  • Action 能夠包含任意異步操做,而 Mutations 必須是同步函數。

通常在組件中進行 dispatch 調用 Actions 方法

this.$store.dispatch('increment');

Vuex 官方針對 Vuex 的最佳實踐,給出了一個項目模板結構,但願你們都能按照這種模式去組織咱們的項目。

├── index.html
├── main.js
├── api
│   └── ... # 抽取出API請求
├── components
│   ├── App.vue
│   └── ...
└── store
    ├── index.js          # 咱們組裝模塊並導出 store 的地方
    ├── actions.js        # 根級別的 action
    ├── mutations.js      # 根級別的 mutation
    └── modules
        ├── cart.js       # 購物車模塊
        └── products.js   # 產品模塊

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

七. 組件註冊方式

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

1. 全局註冊

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

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

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

// 註冊
Vue.component('my-component', {
  template: '<div>A custom component!</div>'
})
// 建立根實例
new Vue({
  el: '#example'
})

<div id="example">
  <my-component></my-component>
</div>

2. 局部註冊

全局註冊組件會拖慢一些頁面的加載速度,有些組件只須要用的到時候再加載,因此沒必要在全局註冊每一個組件。因而就有了局部註冊的方式。

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

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: https://halfrost.com/vue_ios_modularization/

相關文章
相關標籤/搜索