深入理解Vue.js轻量高效的前端组件化方案

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

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操作都被合理地封装在指令定义中,业务代码只需要涉及模板和对数据状态的操作即可,这使得应用的开发效率和可维护性都大大提升。

与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。脏检查有两个问题:
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的语言特性所限。],数据操作的逻辑更为清晰流畅,和第三方数据同步方案的整合也更为方便。

组件系统

在大型的应用中,为了分工、复用和可维护性,我们不可避免地需要将应用抽象为多个相对独立的模块。在较为传统的开发模式中,我们只有在考虑复用时才会将某一部分做成组件;但实际上,应用类 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

总结

以上所述是小编给大家介绍的Vue.js轻量高效的前端组件化方案,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • 详解vue.js组件化开发实践

    前言 公司目前制作一个H5活动,特别是有一定统一结构的活动,都要码一个重复的轮子.后来接到一个基于模板的活动设计系统的需求,便有了下面的内容.借油开车. 组件化 需求一到,接就是怎么实现,技术选型自然成为了第一个问题.鉴于目前web前端mvvm框架以及组件化开发方式的流行,决定技术栈采用:vue + es6 + 组件化. 这里首先简单说下web前端组件化开发方式的历程: 最早的组件化结构,代码结构可能如下: - lib/components/calendar |- calendar.css |-

  • webpack+vue.js实现组件化详解

    简介 在vue中实现组件化用到了vue特有的文件格式.vue,在每一个.vue文件就是一个组件,在组件中我们将html,css,js全部写入,然后在webpack中配置vue-loader就可以了. 建立vue组件 在src目录下建立components文件夹,并在其中建立app.vue文件,这样我们项目的目录结构如下: |--dist //webpack打包后生成的文件夹 | |--build.js |--node_modules //项目的依赖所在的文件夹 |--src //文件入口 | |

  • Vue.JS实现垂直方向展开、收缩不定高度模块的JS组件

    需求分析: 如图,有很多高度不固定的模块(图中只显示两个,本人项目有十三个),点击模块标题展开相应的模块,再次点击此模块匿藏,如何实现此需求并实现复用? 点击红框前: 点击后: 难点分析: 模块高度不固定.比如,本人一开始想到的方法如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title

  • 使用vue.js在页面内组件监听scroll事件的方法

    思路:scroll在哪儿个组件内,就在获取那个dom元素.网上好多思路是 window.addEventListener("scroll", function(){ console.log('scrolling'); }); 这是监听不到的!如果你整个网页可以滑动,或许还可以试试! 对于像我这样,只在页面的内的一个div内要监听的. 实现代码如下: 第一步:滑动的组件外层的div加 ref="viewBox" 为了通过$refs获取dom元素 <!--设备列表

  • 深入理解Vue.js轻量高效的前端组件化方案

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

  • Vue.js React与Angular流行前端框架优势对比

    目录 Vue.js.React和Angular对比 以下是Vue.js的代码示例: 以下是React的代码示例: 以下是Angular的代码示例: Vue.js.React和Angular对比 Vue.js.React和Angular都是流行的前端框架,它们都有自己的优势和劣势. 以下是它们的比较: Vue.js Vue.js是一个轻量级的前端框架,它的核心库只有18KB,因此整个框架的体积很小.Vue.js通过简单的API和组件化的架构,使得开发更加简单,易于上手和维护.Vue.js支持双向数

  • Vue.js实现可排序的表格组件功能示例

    本文实例讲述了Vue.js实现可排序的表格组件功能.分享给大家供大家参考,具体如下: 我们基于 Vue.js 实现一个可根据某列进行排序的表格组件. 一个表格包含表头和数据两部分内容.因此,我们定义两个数组,columns 表示表头信息,在 <thread> 中渲染,并可在此指定某一列是否需要排序:data 表示数据. html: <div id="app" v-cloak> <v-table :data="data" :columns

  • 深入理解Vue.js源码之事件机制

    写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出. 文章的原地址:https://github.com/answershuto/learnVue. 在学习过程中,为Vue加上了中文的注释https://github.com/answershuto/learnVue/tree/master/vue-src,希望可以对其他想学习Vue源码的小伙伴有所帮助. 可能会有理解存在偏差的地方,欢迎提issue指出

  • 深入理解vue.js中的v-if和v-show

    本文主要给大家介绍了关于vue.js中v-if和v-show的相关内容,分享出来供大家参考学习,下面来一起看看详细的介绍: 1.共同点 都是动态显示DOM元素 2.区别 (1)手段:v-if是动态的向DOM树内添加或者删除DOM元素:v-show是通过设置DOM元素的display样式属性控制显隐: (2)编译过程:v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件:v-show只是简单的基于css切换: (3)编译条件:v-if是惰性的,如果初始条件为假,

  • 深入理解vue.js中$watch的oldvalue与newValue

    $watch中的oldvalue和newValue 大家都知道,在vue.js中给我们提供了$watch的方法来做对象变化的监听,而且在callback中会返回两个对象,分别是oldValue和newValue. 顾名思义,这两个对象就是对象发生变化前后的值. 但是在使用过程中我发现这两个值并不总是预期的.下面来一起看看详细的介绍: 定义data的值 data: { arr: [{ name: '笨笨', address: '上海' }, { name: '笨笨熊', address: '北京'

  • 深入理解vue.js双向绑定的实现原理

    前言 大家都知道Vue.js最核心的功能有两个,一是响应式的数据绑定系统,二是组件系统.本文仅探究几乎所有Vue的开篇介绍都会提到的hello world双向绑定是怎样实现的.先讲涉及的知识点,再参考源码,用尽可能少的代码实现那个hello world开篇示例. 一.访问器属性 访问器属性是对象中的一种特殊属性,它不能直接在对象中设置,而必须通过defineProperty()方法单独定义. var obj = { }; // 为obj定义一个名为hello的访问器属性 Object.defin

  • vue.js如何将echarts封装为组件一键使用详解

    前言 本文主要给大家介绍了关于vue.js将echarts封装为组件一键使用的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 说明 做项目的时候为了让数据展示的更加直观,总会用到图表相关的控件,而说到图表控件第一时间当然想到ECharts这个开源项目,而它不像iview.element-ui这些组件使用起来那么便捷,需要绕一个小弯,为了图方便于是对ECharts进行了一层封装 控件演示 控件使用 概要 基于echarts的二次封装 由数据驱动 控件源码见src/com

  • vue.js实现仿原生ios时间选择组件实例代码

    前言 最近几个月一直在看VUE,然后试着只用原生js+vue实现某些组件. PC端时间选择组件 这是最开始实现的pc上的时间选择,平时移动端也在做,所以就想实现一下移动端的时间选择器,下面分享一下我实现移动端滚轮特效时间选择器的思路和过程.整个组件是基于vue-cli来进行构建的 功能 1.时间选择[ A.年月日选择 B.年月日小时分钟选择 C.小时分钟选择 D.分钟选择] 2.滚轮效果[ A.构成一个圆环首尾相连 B.不构成首尾相连] 3.时间选择范围设置(所选时间超过范围将弹窗提示),分钟间

  • Vue.js移动端左滑删除组件的实现代码

    左滑删除在移动端很常见.下面我们一起来封装一下这个简单的小组件.我们想要是: 当滑块没有超过删除按钮的一半时自动回到起点位置. 滑动距离超过一半滑动到最大值(删除按钮宽度) 尽量精简代码 效果如下: 在开始之前,我们先得将 [touchEventApi][1]弄清楚了.这个小组件中,用到了: 1.  TouchEvent.touches (表示一 个 TouchList 对象,包含了所有当前接触触摸平面的触点的Touch对象) 2.  TouchEvent.changedTouches (一个

随机推荐