Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架,是当下很火的一个 JavaScript MVVM 库,是以 数据驱动和组件化 的思想构建的。
MVVM 模式简述
下图不仅概括了 MVVM 模式 (Model-View-ViewModel),还描述了在 Vue.js 中 ViewModel 是如何和 View 以及 Model 进行交互的。
ViewModel 是 Vue.js 的核心,它是一个 Vue 实例。Vue 实例是作用于某一个 HTML 元素上的,这个元素可以是 HTML 的 body
元素,也可以是指定了 id
的某个元素。
当创建了 ViewModel 后,双向绑定是如何达成的呢?
首先,我们将上图中的 DOM Listeners 和 Data Bindings 看作两个工具,它们是实现双向绑定的关键。
- 从 View 侧看,ViewModel 中的 DOM Listeners 工具会帮我们监测页面上DOM元素的变化,如果有变化,则更改Model中的数据;
- 从 Model 侧看,当我们更新 Model 中的数据时,Data Bindings 工具会帮我们更新页面中的 DOM 元素。
库和框架的区别
在这里我们需要稍微注意一下前端 库(Library) 和 框架(Framework) 的区别,它们的本质都是某人编写的,用于解决常见问题的 可复用代码 的集合。
比如,你有一个处理字符串的程序,你为了保持代码的 DRY (Don't Repeat Yourself),你编写了如下可复用的功能代码:
代码语言:javascript复制function getWords(str) {
const words = str.split(' ');
return words;
}
function createSentence(words) {
const sentence = words.join(' ');
return sentence;
}
恭喜你,你创建了一个 JavaScript 库!
如果我们用 「构建房子」 来类比 「构建应用」 的话,那么 使用库 就像是 去宜家购物 一样,我已经有了一个家,现在我需要挑选自己喜欢的一些家具,以达到我自己满意的状态,这一切 都在我的控制范围之内;而 使用框架 就会像是已经有了一个 清装房,在已经规划好的蓝图和选择之中,我们的一些想法会显得十分地有限。
Vue.js 本身只是一个 JavaScript 库,包括 React 也一样,只不过平时我们所说的 Vue 框架,是指包含 Router/ Vuex 等一系列组件之后融合的 一整套解决方案。
更加详细的解释如下:
- 「库」 是一个封装好的特定的集合,提供给开发者使用,而且是特定于某一方面的集合(方法和函数),库没有控制权,控制权完全在于使用者本身;
- 「框架」 顾名思义是一套架构,会基于自身的特点向用户提供一套比较完整的解决方案,如果使用者选定了一套框架,那么就需要根据框架本身做出一定的适应。
02. 为什么使用 Vue?
说实话,我个人非常喜欢 Vue。在我大学刚尝试学习 HTML CSS JavaScript 和 Bootstrap 融合之后,我就接触了 Vue,它对我来说这样的「前端小白」来说,几乎没有什么开发的门槛,很平滑地就得以过渡到 Vue 的使用中去。
典型的 .vue
文件可以简单成如下的样子:(vue-tutorial/typical-case.html)
<template>
<!-- HTML 代码 -->
</template>
<script>
// JavaScript 代码
</script>
<style>
/* css 代码 */
</style>
另外我也非常喜欢尤大大本人,大家可以去看一看 Honeypot 记录的关于 Vue 的 纪录片 (趁着写文的间隙我又看了一遍),当然如果英文有些吃力也可以围观一下在 B 站上的 带中文字幕的版本。
B 站翻译版本截图
Vue 从一开始的定位就是尽可能的降低前端开发的门槛,让更多的人能够更快地上手开发。———— 尤雨溪
理由一:易上手、学习曲线平滑
就像上面的典型 .vue
文件的展示一样,在 Vue 中,一切都很自然,例如我们使用 Vue 来构建我们的 Hello World 程序:(vue-tutorial/hello-vue.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello Vue!</title>
</head>
<body>
<div id="app">
{{ message }}
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 创建一个 Vue 实例或者说是 VieModel 实例
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
</script>
</body>
</html>
可以看到几乎没有多余的部分,只是在创建 Vue 实例时,把 id
为 app
的对象 (此处为一个 div
) 绑定到了 Vue 实例中而已。
理由二:文档友好
由于 Vue 是国人编写的,所以在官网中有完整的中文文档可供开发者参考,并且借由尤大大出色的文笔,非常地清晰易懂,相信看过的朋友会和我有一样的感受:
官方文档的地址可戳 这里
理由三:MVVM 天然的双向绑定
Vue.js 是一个提供了 MVVM 风格的双向数据绑定的 JavaScript 库,这就让我们能够专注于 View 层的开发,这种轻量级的框架让前端开发更加高效、便捷。
例如,我们使用 v-model
来简单改造一下我们的 hello-vue.html
文件让它编程一个简单的双向绑定示例:(vue-tutorial/v-model-demo.html)
<div id="app">
<p>{{ message }}</p>
<input type="text" v-model="message" />
</div>
将 message
绑定到文本框,当更改文本框的值的时候, <p>{{ message }}</p>
中的内容也会被更新:
反过来如果我们更改 message
的值的话,文本框的值也会被更新,我们可以在控制台中尝试一下:
千万不要把框架能力看得比你解决问题的能力还重要
这里是借鉴了 知乎中的一个讨论,不论是使用 React 还是 Vue,我们最终还是要以 解决实际的问题 为出发点。引用一下尤大大在 知乎-Vue 和 React 的优点分别是什么? 上的回答部分截取:
说了这么多,无非是希望大家能停下来想想所谓的 ”A 技术比 B 技术牛逼“ 背后到底是在争些什么,我们使用这些技术的初衷又是什么。很多时候你说这方面,他说那方面,鸡同鸭讲,即使说到一起去,也往往缺乏对等的信息量或者基础共识,只是各自表达主观看法,最后变成两个阵营各自抱团取暖... 说到底,就算你证明了 A 比 B 牛逼,也不意味着你或者你的项目就牛逼了... 比起争这个,不如多想想怎么让自己变得更牛逼吧。—————— 尤雨溪
03. Vue 常用指令
上面我们已经实际体验了一个 Vue 的指令 v-model
了,在 Vue 中,指令都带有 v-
前缀,以表示它们是 Vue 提供的特殊的 attribute
,它们会在渲染 DOM 时进行特殊的响应式行为。
Vue 内置了一些常用的指令,接下来我们将依次来介绍:
-
v-if
和v-else
条件渲染指令; -
v-show
条件展示指令; -
v-for
列表渲染指令 -
v-bind
条件绑定指令; -
v-on
事件处理指令;
大部分照搬的官方教程,写得非常具有参考性,感兴趣的朋友可以直接略过下面部分去参考 官方文档。
v-if 和 v-else 条件渲染指令
v-if 指令
v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy
值的时候被渲染。例如:(vue-tutorial/v-if-demo)
<p v-if="seen">现在你看到我了!</p>
代码语言:javascript复制var app = new Vue({
el: '#app',
data: {
seen: true
}
})
页面会正确的显示「现在你看到我了!」这几个字。
v-else 指令
你也可以使用 v-else
来添加一个 "else 块" 来表达条件不满足时应该渲染的模块:
<p v-if="seen">现在你看到我了!</p>
<p v-else>Oh no!</p>
代码语言:javascript复制var app = new Vue({
el: '#app',
data: {
seen: false
}
})
此时条件 seen
不满足,页面就会显示「Oh no!」的字样。
v-else-if 指令
这是 2.1.0
版本新增的指令,充当 v-if
的 "else-if 块",可以用来连续判断条件:
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
类似于 v-else
,v-else-if
也必须紧跟在带 v-if
或者 v-else-if
的元素之后。
v-show 条件展示指令
另一个用于根据条件展示元素的选项是 v-show
指令。用法大致一样:
<h1 v-show="ok">Hello!</h1>
不同的是带有 v-show
的元素始终会被渲染并保留在 DOM 中。v-show
只是简单地切换元素的 CSS 属性 display
(条件不满足则把元素 display 属性设置为 none),而 v-if
则在条件不满足时直接不渲染出对象。
v-if 与 v-show
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if
也是 惰性 的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show
较好;如果在运行时条件很少改变,则使用 v-if
较好。
v-for 列表渲染指令
我们可以用 v-for
指令基于一个数组来渲染一个列表。v-for
指令需要使用 item in items
形式的特殊语法,其中 items
是源数据数组,而 item
则是被迭代的数组元素的别名。
<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
代码语言:javascript复制var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
结果:
- Foo
- Bar
注意:永远不要把 v-if 和 v-for 同时用在同一个元素上。 因为当 Vue 处理指令时,
v-for
比v-if
拥有更高的优先级,所以会导致错误,详细的技术细节可以 戳这里
v-bind 条件绑定指令
我们可以传给 v-bind:class
一个对象,以动态地切换 class:(也可以用缩写 :
来替代 v-bind
指令)
<div v-bind:class="{ active: isActive }"></div>
上面的语法表示 active
这个 class 存在与否将取决于数据属性 isActive
的 truthiness
。
你可以在对象中传入更多属性来动态切换多个 class。此外,v-bind:class
指令也可以与普通的 class 属性共存。当有如下模板:
<div
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>
和如下 data:
代码语言:javascript复制data: {
isActive: true,
hasError: false
}
结果渲染为:
代码语言:javascript复制<div class="static active"></div>
当 isActive
或者 hasError
变化时,class 列表将相应地更新。例如,如果 hasError
的值为 true
,class 列表将变为 "static active text-danger"
。
v-on 事件处理指令
可以用 v-on
指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。(也可以用缩写 @
来替代 v-on
指令)
示例:
代码语言:javascript复制<div id="example-1">
<button v-on:click="counter = 1">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</div>
代码语言:javascript复制var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})
结果:
image
v-on 的事件修饰符
在事件处理程序中调用 event.preventDefault()
或 event.stopPropagation()
是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。
为了解决这个问题,Vue.js 为 v-on
提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。
.stop
.prevent
.capture
.self
.once
.passive
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
另外事件处理还可以支持 按键码 (某一个键按下)、系统修饰符 (键盘鼠标按下),可以参看 官方教程
04. Todo-List 示例
上面我们了解了一些基本的指令了,接下来我们实际动动手,来搭建一个简单的 TodoList demo 小程序。
第一步:明确需求
TodoList 想必大家都很熟悉,使用来记录我们接下来要做的一些事情的程序,最基本的功能有增加和删除:
很简单,可以看出我们只需要一个输入框 (用来记录将要保存的数据),一个按钮 (用来添加数据),和一个集合 (用来保存数据) 就差不多可以了,上手!
第二步:创建好需要的 data
先来创建好我们需要的数据 data:
代码语言:javascript复制data: {
todos: [{
id: nextTodoId ,
text: '写代码'
},
{
id: nextTodoId ,
text: '还是写代码'
}
],
newTodoText: ""
}
这里多定义了 id
属性是为了方便我们的删除操作。
第三步:创建好对应的 HTML
没有任何布局,就直接定义好我们所需要的组件就好了:
代码语言:javascript复制<input type="text" v-model="newTodoText" />
<button @click="addItem">添加</button>
<ul>
<li v-for="item in todos">
<span>{{ item.text }}</span>
<span><button @click="removeItem(item.id)">del</button></span>
</li>
</ul>
没有任何的特别,只是里面包含了两个我们 未定义
的方法:addItem
和 removeItem
而已。
第三步:定义并实现方法
Vue 中的方法需要定义在 Vue 实例的 methods
关键字下面:
methods: {
addItem(key) {
this.todos.push({
id: nextTodoId,
text: this.newTodoText
})
this.newTodoText = ""
},
removeItem(id) {
this.todos = this.todos.filter(todo => {
return todo.id !== id
})
}
}
这里数组的更新需要用到 push
,另外删除时我们使用了一个 lambda 表达式来完成,删除时传入了一个要删除元素的 id
,然后从数组中挑选出所有 不等于 这个 id
的元素重新赋值给原数组,这样就相当于是删除了元素了。
本文涉及的所有代码都上传到了【More Than Java】项目中。(地址下方)
更好的参考
上面的代码仅仅是简单实现,更好的参考可以查看 Vue 官方实现的一个更加具有参考性的例子:https://codesandbox.io/s/o29j95wx9
参考资料
- Vue【官方文档】 - https://cn.vuejs.org/v2/guide/
- 【译】框架与库的差异 - https://juejin.im/post/5c5fe3e751882561dd7b4e9b
- Vue.js——60分钟快速入门 - https://www.cnblogs.com/keepfool/p/5619070.html