Vue.js 是一个渐进式 MVVM 框架,目前被广泛使用,也成为目前前端技术中颇具代表性的一个框架。
按 Vue 作者的说法,Vue(及其生态)是一个渐进式 MVVM 框架,可以按照实际需要逐步进阶使用更多特性。
声明式渲染
简单来说,声明式渲染即希望开发者更多地表达 “想要什么”,而不用太关心想要的效果的实现细节。例如,我们希望页面上有一个 2x2 的表格,而命令式的实现方式是使用 Canvas 自己画:
// 获取画面和context
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 定义宽高和行列数
const width = 200, height = 100;
const colCount = 2, rowCount = 2;
// todo:定义画笔颜色
// 画横线
for(let i = 0; i <= rowCount; i++){
ctx.moveTo(0, height / rowCount * i);
ctx.lineTo(width, height / rowCount * i);
}
// 画竖线
for(let i = 0; i <= colCount; i++){
ctx.moveTo(width / colCount * i);
ctx.lineTo(width / colCount * i, height);
}
document.body.appendChild(canvas);
我们需要自己关心实现细节,例如每一条线的坐标、每一条表格线的绘制等等。所幸 HTML 为我们提供了 <table>
元素,可以让我们更简单地实现表格:
<table>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
</table>
相比 Canvas 的表格,我们只需要说明需要多少行,每一行有多少个单元格即可,不用自己关注绘制细节。这个例子可以让我们对 “声明式渲染” 有一个更直观的认知。
而在 Vue 中,开发者可以直接通过模板指令来表达 “想要什么”,并不用关心它的底层实现。常见的指令如 v-if
、v-for
、v-show
等都是同样的作用。此外 Vue 还可以声明式地表达对数据的渲染逻辑,例如用 {{message}}
来表示 “在此处显示变量 message
的值”,而不用关心 message
的变量是如何被填入 DOM 对象中。
组件系统
每一个程序员都知道 “高内聚 低耦合” 的编程原则,也都知道代码复用的重要性。但在前端代码中如何实现 “高内聚 低耦合”,以及前端代码复用,并不是一件容易的事情。对于 JavaScript,尚可以使用模块化来解决内聚和复用的问题,但一旦涉及到结构(HTML)和样式,事情就不容易了。
Vue 为开发者提供了 “组件” 的概念,一个组件即一组关联的结构和逻辑。组件内部可以方便地使用声明式渲染将逻辑和结构关联起来,实现组件的高内聚。例如 Vue 官方文档的一个组件例子:
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
这个例子中,template
中定义了组件的结构,并且声明式地将 count
变量显示到 <button>
的内容中。在 <button>
被点击时改变 count
的值。可以看到,组件内部的结构和逻辑都是非常容易互相访问和操作的。而在组件外部,不管是 count
变量还是 <button>
结构,都无法直接进行访问和操作。如果需要外部进行访问和操作,则需要定义相应的接口或者事件。这非常符合 “高内聚 低耦合” 的思想。
组件一旦定义好之后即可被重复使用:
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
从而很好地解决前端代码复用的问题。
客户端路由和状态管理
针对单页面应用,客户端路由的支持是必不可少的一个功能。通过客户端路由,开发者可以根据不同的 URL 加载不同的前端组件(有时候也称为 “页面”),也可以允许用户通过跳转的形式在不同的前端组件之间跳转。
要支持客户端路由,首先需要对当前 URL 进行解析,获取当前访问的页面地址以及参数等信息,然后根据开发者定义的路由与组件的对应关系(路由表)找到页面地址对应的组件,并负责加载和运行组件。
Vue 官方的客户端路由被放到一个独立的库 vue-router
中。开发者也可以根据需要选用。
随着应用复杂度的上升,页面的数据会越来越多,且同一个数据关联多个组件的情况会越来越多。此时数据如何组织、如何定义与组件的关联关系、如何确保变更会同步到每一个关联的组件中就变得非常重要。此时可能就需要使用 “状态管理” 的库来辅助开发者进行页面状态的管理。
状态管理库会提供全局的状态(state
)对象,并定义一系列读写状态的方法。组件中只要使用状态管理提供的读写方法读写状态即可。
Vue 官方也提供了状态管理库 vuex
,可供复杂应用使用。
编译 / 构建工具
借助构建工具的力量,Vue 还可以提供更好的开发体验。例如基于 webpack 强大的 loader 机制,style-loader
和 css-loader
可以允许开发者在 JS 文件中直接引入 CSS 文件。
借鉴这一思路,Vue 推出了单文件组件(Single File Component,简称 SFC)格式.vue
,即允许开发者将同一组件的结构、样式、逻辑全部写在同一个文件中,然后由 vue-loader
在编译阶段将结构和逻辑都编译成 JS 文件,将 CSS 部分借用上述 style-loader
和 css-loader
同样的机制挂载到页面中。
单文件组件的推出使得 Vue 的文件组织又上一个台阶,我们可以更明确地在文件层面就定义好组件,同时由于 vue-loader
的支持,组件也可以通过各种机制(例如 npm)在不同项目间得以复用。
除了单文件组件之外,Vue 也推出了 CLI 工具 vue-cli
,方便开发者更好地初始化项目、搭建项目开发环境并完成构建工作。
总结
通过上述说明,希望大家能更好地理解 “Vue 是一个渐进式的 MVVM 框架” 的含义,它允许我们只用声明式渲染这一点点特性,也允许我们最后加上客户端路由和状态管理来支持大型项目。正因为这样的设计,使得 Vue 上手也非常容易,不用一次学习所有特性的使用。
了解 Vue 渐近式的含义,也可以让我们在后续的源码解读中能清楚地知道每一章的源码在 Vue 生态中的位置。