Vue.js:輕量高效的前端組件化方案

MVVM 數據綁定

MVVM的本質是經過數據綁定連接View和Model,讓數據的變化自動映射爲視圖的更新。Vue.js在數據綁定的API設計上借鑑了Angular的指令機制:用戶能夠經過具備特殊前綴的HTML 屬性來實現數據綁定,也可使用常見的花括號模板插值,或是在表單元素上使用雙向綁定:前端

<!-- 指令 -->
<span v-text="msg"></span>
<!-- 插值 -->
<span>{{msg}}</span>
<!-- 雙向綁定 -->
<input v-model="msg">  

插值本質上也是指令,只是爲了方便模板的書寫。在模板的編譯過程當中,Vue.js會爲每一處須要動態更新的DOM節點建立一個指令對象。每當一個指令對象觀測的數據變化時,它便會對所綁定的目標節點執行相應的DOM操做。基於指令的數據綁定使得具體的DOM操做都被合理地封裝在指令定義中,業務代碼只須要涉及模板和對數據狀態的操做便可,這使得應用的開發效率和可維護性都大大提高。vue

圖1 Vue.js的MVVM架構webpack

 

與Angular不一樣的是,Vue.js的API裏並無繁雜的module、controller、scope、factory、service等概念,一切都是以「ViewModel 實例」爲基本單位:git

 

<!-- 模板 -->
<div id="app">
    {{msg}}
</div>

 

// 原生對象即數據
var data = {
    msg: 'hello!'
}
// 建立一個 ViewModel 實例
var vm = new Vue({
    // 選擇目標元素
    el: '#app',
    // 提供初始數據
    data: data
})

 

渲染結果:github

 

<div id="app">
    hello!
</div>  

 

在渲染的同時,Vue.js也已經完成了數據的動態綁定:此時若是改動data.msg的值,DOM將自動更新。是否是很是簡單易懂呢?除此以外,Vue.js對自定義指令、過濾器的API也作了大幅的簡化,若是你有Angular的開發經驗,上手會很是迅速。web

數據觀測的實現

Vue.js的數據觀測實現原理和Angular有着本質的不一樣。瞭解Angular的讀者可能知道,Angular的數據觀測採用的是髒檢查(dirty checking)機制。每個指令都會有一個對應的用來觀測數據的對象,叫作watcher;一個做用域中會有不少個watcher。每當界面須要更新時,Angular會遍歷當前做用域裏的全部watcher,對它們一一求值,而後和以前保存的舊值進行比較。若是求值的結果變化了,就觸發對應的更新,這個過程叫作digest cycle。髒檢查有兩個問題:數組

 

  1. 任何數據變更都意味着當前做用域的每個watcher須要被從新求值,所以當watcher的數量龐大時,應用的性能就不可避免地受到影響,而且很難優化。
  2. 當數據變更時,框架並不能主動偵測到變化的發生,須要手動觸發digest cycle才能觸發相應的DOM 更新。Angular經過在DOM事件處理函數中自動觸發digest cycle部分規避了這個問題,但仍是有不少狀況須要用戶手動進行觸發。

 

Vue.js採用的則是基於依賴收集的觀測機制。從原理上來講,和老牌MVVM框架Knockout是同樣的。依賴收集的基本原理是:瀏覽器

 

  1. 將原生的數據改形成 「可觀察對象」。一個可觀察對象能夠被取值,也能夠被賦值。
  2. 在watcher的求值過程當中,每個被取值的可觀察對象都會將當前的watcher註冊爲本身的一個訂閱者,併成爲當前watcher的一個依賴。
  3. 當一個被依賴的可觀察對象被賦值時,它會通知全部訂閱本身的watcher從新求值,並觸發相應的更新。
  4. 依賴收集的優勢在於能夠精確、主動地追蹤數據的變化,不存在上述提到的髒檢查的兩個問題。但傳統的依賴收集實現,好比Knockout,一般須要包裹原生數據來製造可觀察對象,在取值和賦值時須要採用函數調用的形式,在進行數據操做時寫法繁瑣,不夠直觀;同時,對複雜嵌套結構的對象支持也不理想。

 

Vue.js利用了ES5的Object.defineProperty方法,直接將原生數據對象的屬性改造爲getter和setter,在這兩個函數內部實現依賴的收集和觸發,並且完美支持嵌套的對象結構。對於數組,則經過包裹數組的可變方法(好比push)來監聽數組的變化。這使得操做Vue.js的數據和操做原生對象幾乎沒有差異[注:在添加/刪除屬性,或是修改數組特定位置元素時,須要調用特定的函數,如obj.$add(key, value)才能觸發更新。這是受ES5的語言特性所限。],數據操做的邏輯更爲清晰流暢,和第三方數據同步方案的整合也更爲方便。babel

圖2 Vue.js的數據觀測和數據綁定實現圖解架構

 

組件系統

在大型的應用中,爲了分工、複用和可維護性,咱們不可避免地須要將應用抽象爲多個相對獨立的模塊。在較爲傳統的開發模式中,咱們只有在考慮複用時纔會將某一部分作成組件;但實際上,應用類 UI 徹底能夠看做是所有由組件樹構成的:

 

 圖3 UI = 組件樹

所以,在Vue.js的設計中將組件做爲一個核心概念。能夠說,每個Vue.js應用都是圍繞着組件來開發的。

註冊一個Vue.js組件十分簡單:

 

 

Vue.component('my-component', {
    // 模板
    template: '<div>{{msg}} {{privateMsg}}</div>',
    // 接受參數
    props: {
        msg: String<br>    

    },
    // 私有數據,須要在函數中返回以免多個實例共享一個對象
    data: function () {
        return {
            privateMsg: 'component!'
        }
    }
})

 

 

註冊以後便可在父組件模板中以自定義元素的形式調用一個子組件: 

 

<my-component msg="hello"></my-component>

 

渲染結果:

 

<div>hello component!</div>

 

Vue.js的組件能夠理解爲預先定義好了行爲的ViewModel類。一個組件能夠預約義不少選項,但最核心的是如下幾個:

 

  • 模板(template):模板聲明瞭數據和最終展示給用戶的DOM之間的映射關係。
  • 初始數據(data):一個組件的初始數據狀態。對於可複用的組件來講,這一般是私有的狀態。
  • 接受的外部參數(props):組件之間經過參數來進行數據的傳遞和共享。參數默認是單向綁定(由上至下),但也能夠顯式地聲明爲雙向綁定。
  • 方法(methods):對數據的改動操做通常都在組件的方法內進行。能夠經過v-on指令將用戶輸入事件和組件方法進行綁定。
  • 生命週期鉤子函數(lifecycle hooks):一個組件會觸發多個生命週期鉤子函數,好比created,attached,destroyed等等。在這些鉤子函數中,咱們能夠封裝一些自定義的邏輯。和傳統的MVC相比,能夠理解爲 Controller的邏輯被分散到了這些鉤子函數中。
  • 私有資源(assets):Vue.js當中將用戶自定義的指令、過濾器、組件等統稱爲資源。因爲全局註冊資源容易致使命名衝突,一個組件能夠聲明本身的私有資源。私有資源只有該組件和它的子組件能夠調用。

 

 

除此以外,同一顆組件樹以內的組件之間還能夠經過內建的事件API來進行通訊。Vue.js提供了完善的定義、複用和嵌套組件的API,讓開發者能夠像搭積木同樣用組件拼出整個應用的界面。這個思路的可行性在Facebook開源的React當中也獲得了印證。

基於構建工具的單文件組件格式

Vue.js的核心庫只提供基本的API,自己在如何組織應用的文件結構上並不作太多約束。但在構建大型應用時,推薦使用Webpack+vue-loader這個組合以使針對組件的開發更高效。

Webpack是由Tobias Koppers開發的一個開源前端模塊構建工具。它的基本功能是將以模塊格式書寫的多個JavaScript文件打包成一個文件,同時支持CommonJS和AMD格式。但讓它不同凡響的是,它提供了強大的loader API來定義對不一樣文件格式的預處理邏輯,從而讓咱們能夠將CSS、模板,甚至是自定義的文件格式當作JavaScript模塊來使用。Webpack 基於loader還能夠實現大量高級功能,好比自動分塊打包並按需加載、對圖片資源引用的自動定位、根據圖片大小決定是否用base64內聯、開發時的模塊熱替換等等,能夠說是目前前端構建領域最有競爭力的解決方案之一。

我在Webpack的loader API基礎上開發了vue-loader插件,從而讓咱們能夠用這樣的單文件格式 (*.vue) 來書寫Vue組件:

 

<style>
.my-component h2 {
  color: red;
}
</style>

<template>
  <div class="my-component">
    <h2>Hello from {{msg}}</h2>
    <other-component></other-component>
  </div>
</template>

<script>
// 遵循 CommonJS 模塊格式
var otherComponent = require('./other-component')

// 導出組件定義
module.exports = {
  data: function () {
    return {
      msg: 'vue-loader'
    }
  },
  components: {
    'other-component': otherComponent
  }
}
</script>

 

同時,還能夠在*.vue文件中使用其餘預處理器,只須要安裝對應的Webpack loader便可: 

 

<style lang="stylus">
.my-component h2
  color red
</style>

<template lang="jade">
div.my-component
  h2 Hello from {{msg}}
</template>

<script lang="babel">
// 利用 Babel 編譯 ES2015
export default {
  data () {
    return {
      msg: 'Hello from Babel!'
    }
  }
}
</script>

 

 

這樣的組件格式,把一個組件的模板、樣式、邏輯三要素整合在同一個文件中,即方便開發,也方便複用和維護。另外,Vue.js自己支持對組件的異步加載,配合Webpack的分塊打包功能,能夠極其輕鬆地實現組件的異步按需加載。

其餘特性

Vue.js還有幾個值得一提的特性:

 

  1. 異步批量DOM更新:當大量數據變更時,全部受到影響的watcher會被推送到一個隊列中,而且每一個watcher只會推動隊列一次。這個隊列會在進程的下一個 「tick」 異步執行。這個機制能夠避免同一個數據屢次變更產生的多餘DOM操做,也能夠保證全部的DOM寫操做在一塊兒執行,避免DOM讀寫切換可能致使的layout。
  2. 動畫系統:Vue.js提供了簡單卻強大的動畫系統,當一個元素的可見性變化時,用戶不只能夠很簡單地定義對應的CSS Transition或Animation效果,還能夠利用豐富的JavaScript鉤子函數進行更底層的動畫處理。
  3. 可擴展性:除了自定義指令、過濾器和組件,Vue.js還提供了靈活的mixin機制,讓用戶能夠在多個組件中複用共同的特性。

 

與Web Components的異同

對Web Components有了解的讀者看到這裏可能會產生疑問:Vue.js的組件和Web Components的區別在哪裏呢?這裏簡要地作一下分析。

Web Components是一套底層規範,自己並不帶有數據綁定、動畫系統等上層功能,所以更合適的比較對象多是Polymer。Polymer在API和功能上和Vue.js比較類似,但它對Web Components的硬性依賴使得它在瀏覽器支持方面有必定的問題——在不支持Web Components規範的瀏覽器中,須要加載龐大的polyfill,不只在性能上會有影響,而且有些功能,好比ShadowDOM,polyfill並無辦法完美支持。同時,Web Components規範自己還沒有定稿,一些具體設計上仍存在不小的分歧。相比之下,Vue.js在支持的瀏覽器中(IE9+)沒有任何依賴。

除此以外,在支持Web Components的環境中,咱們也能夠很簡單地利用Web Components底層API將一個Vue.js組件封裝在一個真正的自定義元素中,從而實現Vue.js組件和其餘框架的無縫整合。

總結

在發佈之初,Vue.js本來是着眼於輕量的嵌入式使用場景。在今天,Vue.js也依然適用於這樣的場景。因爲其輕量(22kb min+gzip)、高性能的特色,對於移動場景也有很好的契合度。更重要的是,設計完備的組件系統和配套的構建工具、插件,使得Vue.js在保留了其簡潔API的同時,也已經徹底有能力擔當起復雜的大型應用的開發。

從誕生起到如今的一年半歷程中,Vue.js經歷了一次完全的重構,屢次API的設計改進,目前已經趨於穩定,測試覆蓋率長期保持在100%,GitHub Bug數量長期保持在個位數,並在世界各地都已經有公司/項目將Vue.js應用到生產環境中。在2015年晚些時候,Vue.js將發佈1.0版本,敬請期待。

【參考連接】

Vue.js官方網站:http://vuejs.org

Vue.js GitHub倉庫:https://github.com/yyx990803/vue

Webpack官方網站: http://webpack.github.io

vue-loader單頁組件示例:https://github.com/vuejs/vue-loader-example

相關文章
相關標籤/搜索