Vue.js:轻量高效的前端组件化方案

转发一篇尤老师对vue.js的介绍,了解vue.js的前因后果。不过如今已是2.0了,也有添加一些新的东西,固然有些东西也改了。前端

Vue.js:轻量高效的前端组件化方案vue

 

Vue.js 是我在2014年2月开源的一个前端开发库,经过简洁的 API 提供高效的数据绑定和灵活的组件系统。在前端纷繁复杂的生态中,Vue.js有幸受到必定程度的关注,目前在 GitHub上已经有5000+的star。本文将从各方面对Vue.js作一个深刻的介绍。

开发初衷

2013年底,我还在Google Creative Lab工做。当时在项目中使用了一段时间的Angular,在感叹数据绑定带来生产力提高的同时,我也感到Angular的API设计过于繁琐,使得学习曲线颇为陡峭。出于对Angular数据绑定原理的好奇,我开始 “造轮子”,本身实现了一个很是粗糙的、基于依赖收集的数据绑定库。这就是Vue.js的前身。同时在实际开发中,我发现用户界面彻底能够用嵌套的组件树来描述,而一个组件偏偏能够对应MVVM中的ViewModel。因而我决定将个人数据绑定实验改进成一个真正的开源项目,其核心思想即是 “数据驱动的组件系统”。webpack

MVVM 数据绑定

MVVM的本质是经过数据绑定连接View和Model,让数据的变化自动映射为视图的更新。Vue.js在数据绑定的API设计上借鉴了Angular的指令机制:用户能够经过具备特殊前缀的HTML 属性来实现数据绑定,也可使用常见的花括号模板插值,或是在表单元素上使用双向绑定:git

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

插值本质上也是指令,只是为了方便模板的书写。在模板的编译过程当中,Vue.js会为每一处须要动态更新的DOM节点建立一个指令对象。每当一个指令对象观测的数据变化时,它便会对所绑定的目标节点执行相应的DOM操做。基于指令的数据绑定使得具体的DOM操做都被合理地封装在指令定义中,业务代码只须要涉及模板和对数据状态的操做便可,这使得应用的开发效率和可维护性都大大提高。github

图1 Vue.js的MVVM架构web

与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的开发经验,上手会很是迅速。babel

数据观测的实现

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的语言特性所限。],数据操做的逻辑更为清晰流畅,和第三方数据同步方案的整合也更为方便。

图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

相关文章
相关标签/搜索