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

2021-02-20 CSDN

Vue.js 是我在2014年2月開源的一個前端開發庫,通過簡潔的 API 提供高效的數據綁定和靈活的組件系統。在前端紛繁複雜的生態中,Vue.js有幸受到一定程度的關注,目前在 GitHub上已經有5000+的star。本文將從各方面對Vue.js做一個深入的介紹。

開發初衷

2013年末,我還在Google Creative Lab工作。當時在項目中使用了一段時間的Angular,在感嘆數據綁定帶來生產力提升的同時,我也感到Angular的API設計過於繁瑣,使得學習曲線頗為陡峭。出於對Angular數據綁定原理的好奇,我開始 「造輪子」,自己實現了一個非常粗糙的、基於依賴收集的數據綁定庫。這就是Vue.js的前身。同時在實際開發中,我發現用戶界面完全可以用嵌套的組件樹來描述,而一個組件恰恰可以對應MVVM中的ViewModel。於是我決定將我的數據綁定實驗改進成一個真正的開源項目,其核心思想便是 「數據驅動的組件系統」。

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操作都被合理地封裝在指令定義中,業務代碼只需要涉及模板和對數據狀態的操作即可,這使得應用的開發效率和可維護性都大大提升。

圖1 Vue.js的MVVM架構

與Angular不同的是,Vue.js的API裡並沒有繁雜的module、controller、scope、factory、service等概念,一切都是以「ViewModel 實例」為基本單位:

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

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

渲染結果:

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

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

數據觀測的實現

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

任何數據變動都意味著當前作用域的每一個watcher需要被重新求值,因此當watcher的數量龐大時,應用的性能就不可避免地受到影響,並且很難優化。

當數據變動時,框架並不能主動偵測到變化的發生,需要手動觸發digest cycle才能觸發相應的DOM 更新。Angular通過在DOM事件處理函數中自動觸發digest cycle部分規避了這個問題,但還是有很多情況需要用戶手動進行觸發。

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

將原生的數據改造成 「可觀察對象」。一個可觀察對象可以被取值,也可以被賦值。

在watcher的求值過程中,每一個被取值的可觀察對象都會將當前的watcher註冊為自己的一個訂閱者,並成為當前watcher的一個依賴。

當一個被依賴的可觀察對象被賦值時,它會通知所有訂閱自己的watcher重新求值,並觸發相應的更新。

依賴收集的優點在於可以精確、主動地追蹤數據的變化,不存在上述提到的髒檢查的兩個問題。但傳統的依賴收集實現,比如Knockout,通常需要包裹原生數據來製造可觀察對象,在取值和賦值時需要採用函數調用的形式,在進行數據操作時寫法繁瑣,不夠直觀;同時,對複雜嵌套結構的對象支持也不理想。

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

圖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> <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 編譯 ES2015export default { data () { return { msg: 'Hello from Babel!' } }}</script>

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

其他特性

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

異步批量DOM更新:當大量數據變動時,所有受到影響的watcher會被推送到一個隊列中,並且每個watcher只會推進隊列一次。這個隊列會在進程的下一個 「tick」 異步執行。這個機制可以避免同一個數據多次變動產生的多餘DOM操作,也可以保證所有的DOM寫操作在一起執行,避免DOM讀寫切換可能導致的layout。

動畫系統:Vue.js提供了簡單卻強大的動畫系統,當一個元素的可見性變化時,用戶不僅可以很簡單地定義對應的CSS Transition或Animation效果,還可以利用豐富的JavaScript鉤子函數進行更底層的動畫處理。

可擴展性:除了自定義指令、過濾器和組件,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

本文選自程式設計師電子版2015年8月A刊(含iPad版、Android版、PDF版)。

本文為CSDN原創,點擊「閱讀原文」可查看原文並參與討論。

如果您喜歡這篇文章,請點擊右上角「…」將本文分享給你的朋友

相關焦點

  • 可能是目前最完整的前端框架 Vue.js 全面介紹
    摘要2016年最火的前端框架當屬Vue.js了,很多使用過vue的程式設計師這樣評價它,「vue.js兼具angular.js和react.js的優點,並剔除了它們的缺點」。授予了這麼高的評價的vue.js,也是開源世界華人的驕傲,因為它的作者是位中國人–尤雨溪(Evan You)。
  • Python Django + 前端Vue.js快速搭建web項目
    三、 構建Vue.js前端項目1、 先用npm安裝vue-cli腳手架工具(vue-cli是官方腳手架工具,能迅速幫你搭建起vue項目的框架2、 在目錄src下包含入口文件main.js,入口組件App.vue等。
  • 如何寫一手漂亮的 Vue
    當然 Vue 還具有其他若干令人擊節讚嘆的設計,這些我們可以參見 Vue 作者尤雨溪的文章:Vue.js:輕量高效的前端組件化方案。鑑於此,如果可以很熟練的掌握其數據的綁定與傳輸,組件的開發,以及周邊 Webpack 等相關配置,則能將運用水平視為進入了一個新的層次。據以往經驗來看,這不是一件容易的事兒,畢竟使用這 Vue 也是衝著解決需求去了,而非在搞研究。
  • Django+Vue.js構建前後端分離項目系列一:基礎篇
    以下是安裝命令備註:系統環境為 Centos7二、創建Django後端和Vue.js前端項目2.1 Django項目django-admin startproject django_singlepage_with_vuepip install django==1.11.3
  • Vue.js輪播庫熱門精選
    在這篇文章中,我們將看看Vue.js的一些輪播圖庫,讓我們的生活變得更輕鬆,並讓我們拿回一些寶貴的時間。vue-easy-slidervue-easy-slider[1] 是一個簡單的Vue滑塊組件,可與滑鼠和觸控螢幕一起使用。它是可自定義的,並帶有動畫效果。
  • 60 個 Vue 常見問題匯總及解決方案
    --// 錯誤例子2--><router-link :to="item.menuUrl" @click="toggleName=''"> <i :class="['fzicon',item.menuIcon]"></i> <span>{{item.menuName}}</span></router-link><!
  • 可能是史上最全面的學習資源 — VUE 開源庫篇(一)
    Markdown編輯器https://github.com/egoist/eme11、vue-multiselect ★1166 - Vue.js選擇框解決方案https://github.com/monterail/vue-multiselect12、vue-table ★824 - 簡化數據表格https://github.com
  • Django 3 + Vue.js 前後端分離Web開發實戰
    6、在frontend目錄src下包含入口文件main.js,入口組件App.vue等。後綴為vue的文件是Vue.js框架定義的單文件組件,其中標籤中的內容可以理解為是類html的頁面結構內容。7、在src/component文件夾下新建一個名為Home.vue的組件,通過調用之前在Django上寫好的api,實現添加書籍和展示書籍信息的功能。
  • Vue學習筆記:vue-cropper實現封裝截圖組件
    vue-cropper一款好用的前端截圖插件項目介紹:使用vue+elementUI
  • (上)Vue+Echarts構建可視化大數據平臺實戰項目分享(附源碼)
    那前端數據可視化又是什麼呢?前端數據可視化其實就是利用前端表現層的手段,以前端手段展示、處理和分析數據。前端因為H5的到來,使前端有了質的飛躍,也使前端數據可視化的飛速發展得到了契機。H5提供的canvas就是這一契機。它是前端利用JS製作在做前端數據可視化的利器,幾乎全部前端數據可視化工具都是基於其上做的。
  • 最新前端 Vue 項目重構總結
    在.vue文件中寫css、less代碼,配置 css和less,使項目正常運行。在.vue文件中引入圖片,字體等,配置 靜態資源,使項目正常運行。在app.vue文件內只引入簡單組件,再嘗試引入一個頁面,使項目正常運行。
  • Python 3+Django 3 結合Vue.js框架構建前後端分離Web開發平臺實戰
    6、在frontend目錄src下包含入口文件main.js,入口組件App.vue等。後綴為vue的文件是Vue.js框架定義的單文件組件,其中標籤中的內容可以理解為是類html的頁面結構內容。7、在src/component文件夾下新建一個名為Home.vue的組件,通過調用之前在Django上寫好的api,實現添加書籍和展示書籍信息的功能。
  • Vue 開源項目庫匯總
    eme ★1390 - 優雅的Markdown編輯器vue-multiselect ★1166 - Vue.js選擇框解決方案vue-table ★824 - 簡化數據表格VueCircleMenu ★776 - 漂亮的vue圓環菜單vue-chat ★748 - vuejs和vuex及webpack的聊天示例radon-ui
  • VUE你想要的這裡都有..
    ★3328 - 通過Vue Material和Vue 2建立精美的app應用vuetify ★2925 - 為移動而生的Vue JS 2組件框架Keen-UI ★2749 - 輕量級的基本UI組件合集vonic ★1913 - 快速構建移動端單頁應用vue-multiselect ★1539 - Vue.js選擇框解決方案eme
  • 前後端通吃,vue大全Mark一下
    ★3328 - 通過Vue Material和Vue 2建立精美的app應用vuetify ★2925 - 為移動而生的Vue JS 2組件框架Keen-UI ★2749 - 輕量級的基本UI組件合集vonic ★1913 - 快速構建移動端單頁應用vue-multiselect ★1539 - Vue.js選擇框解決方案eme
  • 組件系統之SSR動態渲染——DEMO版
    其實不然,每一個方案的落地,可能都是N個想法相互拼殺的結果。團隊維護了一個Landing Page系統,準備將這個系統平臺化,平臺化就需要具備組件的多元化的能力,這樣才能夠適應更多的場景,滿足更多的用戶。目前的所有組件都是標準組件,包含在整個Landing Page平臺中;所以頁面的首屏、SEO和性能完全可以基於同構策略去實現。
  • 分享一個前後端分離方案-前端angularjs+requirejs+dhtmlx 後端asp.net webapi
    前端項目dhtmlx_web:      開發工具 Sublime Text      前端框架angularjs         模塊加載requirejs         前端UI dhtmlx + bt3      包管理 bower         構建工具 gruntjs         服務架設 http_server.js
  • 「Vue實戰」武裝你的項目
    const api = Object.assign({}, http, account, \*...其它模塊*\)export default api1.3 global.js中的處理在 global.js中引入:import Vue from 'vue'import api from '.
  • Vue與React的區別之我見
    from=groupmessagereact和vue都是做組件化的,整體的功能都類似,但是他們的設計思路是有很多不同的。使用react和vue,主要是理解他們的設計思路的不同。1.數據是不是可變的react整體是函數式的思想,把組件設計成純組件,狀態和邏輯通過參數傳入,所以在react中,是單向數據流,推崇結合immutable來實現數據不可變。
  • 能解決 90% 需求的js庫
    create.js[1]一套完整的H5遊戲引擎,做2d的動畫他就足夠了,包括 聲音加載庫:sound.js,預加載庫 :preload.js,canvas動畫庫:ease.js,補間動畫庫:tween.jsFullpage.js[2]快速實現全屏滾動特,基於 jQueryChart.js