Vue 2 基础
Vue 是渐进式 JavaScript 框架,与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。因此既可以嵌入到已有项目中,优化其中一部分代码;也可以基于 Vue 三大套件开发管理大型项目。
Tip
参考:
- Youtube 频道 Alex 宅幹嘛 的《Re Vue 重頭說起》系列教程
- Vue 2 官方文档
实例化
最简单引入 Vue 的方法是使用 CDN
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
使用 new Vue({})
的方式实例化一个 Vue 应用。其中接收的参数是一个选项对象,设置关于 Vue 实例的选项,例如 el
属性是指定挂载的 DOM 节点,data
属性是包含响应式的数据。
Tip
从组件化的角度看,Vue 实例也是一个根组件
<body>
<div id="app"></div>
<script>
// 一般使用变量名 `vm`「存储」 Vue 实例,这是 ViewModel 的缩写
const vm = new Vue({
el: '#app',
data: {
name: 'Ben'
}
})
</script>
</body>
实际上有多种渲染 template 的方式:
- 创建应用时只有
el
选项:指定挂载的 DOM 节点,并以该节点内部原有的 HTML 内容作为模板 template - 创建应用时有
el
选项+template
选项:将 Vue 应用挂载到指定的 DOM 节点,并用template
内容取代掉el
指定的 DOM 节点原有的内容 - 创建应用时只有
template
选项:之后必须调用vm.$mount(el)
方法将应用挂载到el
指定的 DOM 节点,而且会取代掉el
指定的 DOM 节点的内容
因此只要有 template
选项,Vue 应用就会取代掉挂载的 DOM 节点内部的内容
Tip
Vue 实例还暴露了一些有用的实例 property 与方法。它们都有前缀 $
const vm = new Vue({
el: '#app',
data: {
name: 'Ben'
}
})
vm.$data === data // true
vm.$el === document.getElementById('app') // true
// $watch 设置一个侦听器
vm.$watch('name', function (newValue, oldValue) {
// 这个回调将在 `vm.name` 改变后调用
})
视频教程中说到一个小 trick:
如果将 Vue 「嵌入」到原有项目时,应该使用立即调用函数表达式 immediately-invoked function expressions,IIFE 包裹代码,可以模拟构建出块级作用域,代码立即执行并拥有了自己的私有变量,避免污染全局变量。另外在最前面加上 ;
是为了避免一些打包程序在合并代码的时候,错误地将立即执行函数识别为前一行代码中函数的参数。
模板语法
将 Vue 应用挂载到 DOM 以后,该节点内部的 HTML 就会支持模板语法 Mustache,即使用双花括号包含的部分,在其中支持使用简单的 JavaScript 表达式
<template>
<p>{{ content }}</p>
</template>
过滤器
过滤器被用于一些常见的文本格式化。过滤器可以用在两个地方:
- 双花括号插值
v-bind
表达式
<template>
<div>
<!-- 在双花括号中 -->
<p>{{ message | capitalize }}</p>
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
</div>
</template>
过滤器应该被添加在 JavaScript 表达式的尾部,由 「管道」符号 指示 value | filterName
,:IconCustom{name="mdi:youtube" iconClass="text-red-500"} 过滤器是 JavaScript 函数,因此可以接收参数,它总是接收管道号之前的 JavaScript 表达式的值作为第一个参数,主动传入的数据则作为第二个参数开始传递。
计算属性和侦听器
在选项 computed
中创建计算属性,在选项 watch
创建监听器,两者都是基于响应式数据的变化再执行操作的,但用处有不同。
computed
计算属性常常是为了将复杂和需要重用的资料处理逻辑,从模板中的函数表达式提出来。它类似 data
的属性一样,作为响应式数据在模板上直接使用。
计算属性的最大优势是对于响应式依赖有缓存的。如果希望计算属性实时更新,那么它需要有响应式依赖(基于 data 或其他来源的响应式数据),并且返回一个值。
计算属性除了可以使用函数的方式,:IconCustom{name="mdi:youtube" iconClass="text-red-500"} 还可以使用对象的方式,需要为对象设置 get
(默认只有 getter)和 set
属性。
- getter 是基于 data property 响应式数据返回计算的值
- setter 是为了响应用户直接为该计算属性赋值时
computed_property = value
,反过来设定其所依赖的响应数据(即更新 data property)
// ...
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
// set computed property
// the setter will be invoked and vm.firstName and vm.lastName will be updated accordingly.
vm.fullName = 'John Doe';
watch
watch 也是依赖响应式数据的(每个 watch 都只能针对一个响应式数据,一般是 data property,相比较 computed 可以同时侦听多个响应式数据),但它一般是用于基于响应式数据的变化执行异步操作,可以设置中间状态。
// ...
watch: {
a: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
},
// 可以侦听响应式对象中某个属性 watch vm.e.f's value: {g: 5}
'e.f': function (val, oldVal) { /* ... */ }
// 你可以传入回调数组,它们会被逐一调用
e: [
'handle1',
function handle2 (val, oldVal) { /* ... */ },
{
handler: function handle3 (val, oldVal) { /* ... */ },
/* ... */
}
],
}
Tip
不应该使用箭头函数来定义 watcher 函数,因为是箭头函数绑定了父级作用域的上下文,所以函数内的 this
将不会按照期望指向 Vue 实例。
Tip
如果所依赖的响应式数据是一个对象或数组,需要开启 deep
深度侦听选项;如果希望侦听器在实例初始化后立即执行其回调函数,可以开启 immediate
选项。
watch: {
// 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
c: {
handler: function (val, oldVal) { /* ... */ },
deep: true
},
// 该回调将会在侦听开始之后被立即调用
d: {
handler: 'someMethod',
immediate: true
},
}
属性绑定
==在模板中使用指令 v-bind
将标签的属性与动态数据绑定,这样就实现了数据驱动画面(样式)。==
指令 v-bind:attrName="value"
可以简写为 :attrName="value"
,绑定的值可以是字符串、对象、数组等。
Tip
将数据绑定到属性时,:IconCustom{name="mdi:youtube" iconClass="text-red-500"} 由于 HTML 和 JavaScript 对于大小写和连字号的支持不同,留意相应关系。例如在 HTML 元素中设置内联样式可以使用 font-size
,但在 JavaScript 表达式中应该使用 fontSize
或者用引号将其包裹 "font-size"
对于属性 class
和 style
这两个特殊的属性,:IconCustom{name="mdi:youtube" iconClass="text-red-500"} Vue 对这两个 attribute 进行特殊优化,可以将动态绑定的数据与该属性的静态数据进行合并(而其他 attribute 就是「后盖前」)。
class 绑定
标签的属性 class
除了支持绑定字符串,还可以绑定对象和数组,以批量地设置多个 className
,而且会与静态的数据==直接进行拼接合并==
- 如果绑定值为对象,可同时设置多个
className
,并通过对象属性的属性值的布尔类型 true/false 快速动态地切换标签的 class,但新增 newClassName 不方便html<div :class="classObj"> <!-- content --> </div>
js// ... classObj: { className1: true, className2: false }
- 如果绑定值为数组,同时设置多个
className
,可以方便地新增其他属性,只需要将 newClassNamepush
到数组就行,但切换/删除 className 不方便恢复html<div :class="classArr"> <!-- content --> </div>
js// ... classArr: ['className1', 'className2']
style 绑定
标签属性 style
用于添加内联样式,可以绑定对象或数组。
有一些细节需要注意:
- 对于需要单位的样式,需要指定单位
- 如果绑定的样式和静态的样式有冲突,会以==绑定的样式优先==
- 数组允许是对象作为元素
双向绑定
在模板的标签中使用指令 v-model
实现表单的值与动态数据双向绑定,实现表单 <input>
、<textarea>
、<select>
等标签的输入数据修改动态数据变量 data property,当然也可以通过修改相应的动态数据 data property 控制表单的值。
Tip
在文本区域 <textarea>{{text}}</textarea>
并不会生效,应用 v-model
来代替 <textarea v-model="message" placeholder="add multiple lines"></textarea>
v-model
的实现原理是为不同的表单元素监听相应的不同 property 属性,并抛出不同的事件(事件处理函数会修改表单所绑定的相应的响应式变量),实现双向绑定。如果需要客制化可以修改相应的事件处理函数。
Tip
Vue 已经对需要使用输入法(如中文、日文、韩文等) 的语言,v-model
不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用 input
事件。此外 input>
元素在中文输入法时按下 enter 选字,可能会误触发按下 enter 确认送出表单,如果要区分两种情况,可以监听 @compositionstart
和 @compositionend
来判断输入法的开启与关闭。
表单中 <input type="checkbox>"
可以绑定布尔类型的动态数据变量(单选时,推荐使用 type="radio"
更适合),也可以绑定数组类型的动态数据变量(而且数组的元素的顺序和勾选的顺序一致),由于 checkbox 可以是单选也可以是多选。
<!-- 单个复选框,绑定到布尔值 -->
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>
<!-- 多个复选框,绑定到同一个数组 -->
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<script>
new Vue({
el: '...',
data: {
checkedNames: []
}
})
</script>
如果 <input type="checkbox>"
绑定的是布尔类型的动态数据变量,可以通过 true-value
和 false-value
定制不同状态下的值,但是推荐使用 <input type="radio">
并设定属性 value
来定制值,因为浏览器在提交表单时并不会包含未被选中的复选框(即 false-value
并不会提交),如果要确保提交表单时,这两个值中的一个能够被提交,应该使用单选按钮。
元素 <select>
进行双向绑定后,动态数据变量是基于其子元素选中 option
的属性 value
的值进行改变,如果是支持多选就绑定到数组类型的动态数据变量
<div id="example-6">
<select v-model="selected" multiple style="width: 50px;">
<option value="dataA">A</option>
<option value="dataB">B</option>
<option value="dataC">C</option>
</select>
</div>
<script>
new Vue({
el: '#example-6',
data: {
selected: []
}
})
</script>
Vue 提供了一些修饰符,方便地对表单输入值进行设置:
- 表单的值默认是字符串类型,如果希望绑定的动态数据变量保存的是数值类型,可以使用表单修饰符
v-model.number
- 表单修饰符
v-model.trim
自动过滤用户输入的首尾空白字符 - 表单修饰符
v-model.lazy
在change
事件之后(失去焦点后)进行数据同步(默认是在input
事件触发后)
在组件上使用 v-model
在使用自定义的输入型组件时,也可以使用 v-model
实现双向绑定,但是组件内部需要进行相应的处理:
- 在外部(引用该组件时,在组件的标签上)通过指令
v-model
双向绑定需要同步的变量,它的数据作为 prop 传递到组件内(因此记得在组件内部需要在props
选项里声明value
作为 prop),作为子组件内部表单元素的value
属性的值 - 在组件内使用指令
v-bind
将表单元素的value
属性绑定到以上声明的 prop 变量上,即v-bind:value="value"
(这样就可以将外部传进来的值作为表单的值,实现与外部数据的「同步」) - 在组件内使用指令
v-on
监听表单元素input
输入事件,并通过抛出相同的事件$emit('input', $event.target.value)
向外传递相应的数据,然后外部通过指令v-model
绑定的变量就会基于抛出的数据进行改变 (即数据的修改的操作还是在父层完成)
// component
Vue.component('base-checkbox', {
props: ['value'],
template: `
<input
type="text"
v-bind:value="value"
v-on:change="$emit('input', $event.target.value)"
>
`
})
<base-input v-model="inputText"></base-input>
Tip
视频中的第 1 个组件,子组件向外抛出的是自定义事件,因为目的是为待办事项列表添加列表项,这和单纯抛出 change
或 input
事件不同,因此需要在父级监听该自定义事件;如果只是想数据实现「双向同步」,使用 v-model
即可。
条件渲染
指令 v-if
和 v-show
都可以实现条件渲染。
在模板标签中使用指令 v-if
控制 DOM 的渲染,只有条件指令的值为 true
时才渲染元素,否则 DOM 被「拔出」。
还可以使用 v-if
、v-else-if
、v-else
设置同级并列元素的条件渲染,这三个指令的元素需要连续且同级。如果通过条件切换多块类似的元素,可能会因为重用造成一些 Bug,这样可以为元素加上设置不同的 key
属性值,以便 Vue 区分它们。:IconCustom{name="mdi:youtube" iconClass="text-red-500"} 但是并不推荐将业务逻辑直接写在 HTML 里,应该在 JS 里面根据条件直接生成相应的数据。
在模板标签中使用指令 v-show
也是控制 DOM 的渲染,只有条件指令的值为 true
时才显示元素,否则 DOM 被隐藏。
Tip
v-if
和 v-show
都是控制 DOM 在页面上的显示或隐藏,但原理和用处有很大不同:
v-if
是基于条件控制 DOM 是否会进行渲染来显示或隐藏元素;而v-show
是先对 DOM 进行渲染,再根据条件来改变 CSS 属性display
的值,如果要隐藏就设置为display: none
。因此如果需要频繁地进行元素显示隐藏的切换,应该使用v-show
效能更高。- 指令
v-if
可以与<template>
结合使用, 将<template>
可以作为容器,包装需要条件渲染的 DOM,这样就可以控制一堆 DOM 的渲染,同时不会在页面上新增不必要的 tag;但是指令v-show
无法使用<template>
上,由于v-show
需要元素先渲染,再在该元素调整 CSS 的display
属性,因此它只能用在会实际出现在页面的标签 Play Video
列表渲染
在模板标签中使用指令 v-for
可以将响应式数据中的数组或对象渲染为一系列的 DOM 元素,称为列表渲染,常用形式是 v-for item in arr
或 v-for item of arr
- 可以遍历 arr 数组、obj 对象、number(从 1 开始)数字
- 但是遍历 objList (类数组的对象)时候,无法保证每次遍历 item 的先后次序
其中很重要的一点是==需要为生成的元素设置 key
属性==,一般绑定的是与该元素相关的唯一标识(不要直接用 index
),以便提高 DOM 更新的效率。
Tip
由于对象和数组都是引用数据类型,所以修改它们后常常会出现无法正确地触发响应式变化的问题,:IconCustom{name="mdi:youtube" iconClass="text-red-500"} Vue 特意针对数组常用方法(push()
、pop()
、shift()
、unshift()
、splice()
、sort()
、reverse()
)做出优化,如果通过这些方法修改数组,Vue 可以侦听到,相关依赖会做出响应式变化;而==直接通过下标来修改数组的值,往往无法触发视图进行响应式地更新==
v-if
和 v-for
不推荐一起使用,因为指令 v-for
优先级更高,所以无论该元素 v-if
的条件如何都会先执行列表渲染,再通过 v-if
判断已经渲染的 DOM 是否要「拔除」,这样会浪费 Vue 的性能。v-for
应该和 v-show
配合使用。
事件处理
在模板的标签中使用指令 v-on:eventName
监听事件,可以使用简写形式 @eventName
,并触发回调函数。
<div id="example-1">
<!-- 触发事件后直接运行一些 JavaScript 代码 -->
<button v-on:click="counter += 1">Add 1</button>
<!-- 触发事件后调用处理函数 `greet` 是在下面定义的方法名 -->
<button v-on:click="greet">Greet</button>
<!-- 可以为处理函数传递参数 -->
<button v-on:click="say('what')">Say what</button>
</div>
Tip
处理函数默认将 DOM 事件 event
作为传入的参数;如果需要同时向回调函数传递参数和原始的 DOM 事件,:IconCustom{name="mdi:youtube" iconClass="text-red-500"} 可以再传递特殊的变量 $event
,相应地事件处理函数需要设置形参进行接收
<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
// ...
methods: {
warn: function (message, event) {
// 现在我们可以访问原生事件对象
if (event) {
event.preventDefault()
}
alert(message)
}
}
Vue 提供了多种事件修饰符,用于更方便地对事件的行为进行设置,可以链式调用多个
.stop
阻止事件继续传播.prevent
阻止事件的默认行为,例如表单提交后跳转到指定网址.self
当事件在该元素自身触发时才执行事件处理函数.once
只对事件进行一次响应后失效.capture
使用事件捕获模式.passive
针对scroll
事件进行优化,提高性能。但不要把.passive
和.prevent
一起使用,因为.prevent
将会被忽略,同时浏览器可能会向你展示一个警告。
对于 UI 用户界面交互事件是最常见的,如鼠标事件、按键事件等,Vue 提供了鼠标和按键修饰符
- 按键修饰符:
.enter
、.tab
、.delete
(捕获「删除」和「退格」键)、.esc
、.space
、.up
、.down
、.left
、.right
、.ctrl
、.alt
、.shift
、.meta
(在 macOS 上是command
键,在 Windows 上是win
键) - 鼠标按钮修饰符:
.left
、.right
、.middle
.exact
由精确指定的键组合才触发事件
Tip
想单纯监听 ctrl
键放开的操作,需要监听 ctrl
的 keycode
,即 keyup.17
视频教程说到一个小 trick:可以监听 click.right.prevent
鼠标右键点击事件,并阻止弹出菜单的默认行为,这样就可以客制化网页的右键菜单
Tip
使用指令 v-on
设置的事件监听会在 ViewModel 销毁后事件处理器都会自动被删除,但是使用自己使用 addEventlistener
等方法设置的事件监听,:IconCustom{name="mdi:youtube" iconClass="text-red-500"} 记得需要在 beforeDestroy()
生命周期函数中手动解绑,以便释放效能。
自定义指令
指令一般是为了更方便地 对普通 DOM 元素进行操作,Vue 提供多种内置指令,如 v-if
、v-bind
、v-on
、v-model
方便在模板中使用,也允许注册自定义指令。
全局注册
通过 Vue 原型 prototype 上的方法 Vue.directive
注册一个全局可用的自定义指令,第一个参数 directiveName
是指令的后缀名,第二个参数就是定义指令的行为,:IconCustom{name="mdi:youtube" iconClass="text-red-500"} Vue 为自定义指令提供了 5 种钩子函数,可以在所绑定的元素的不同阶段进行调用。然后可以在 Vue 实例中的任何元素中使用该自定义的指令 <tag v-directiveName="value">
Vue.directive('directiveName', {
// 提供 5 种钩子函数备选,在所绑定的元素的不同阶段进行调用
bind: function(el) {},
inserted: function (el) {},
update: function (el) {},
componentUpdated: function (el) {},
unbind: function (el) {},
})
Tip
很多时候可能只想使用 bind
和 update
钩子函数,而且想触发相同的行为,可以使用简写形式 Vue.directive('directiveName', function (el, binding) {...})
指令的钩子函数会传入以下参数,以便操作所绑定的 DOM 元素和传递数据:
el
是指令所绑定的元素,可以用来直接操作 DOMbinding
是一个对象,包含以下 property:name
指令名,不包括v-
前缀value
指令的绑定值,例如v-directiveName="1 + 1"
中,绑定值为 2oldValue
指令绑定的前一个值,仅在update
和componentUpdated
钩子函数中可用。无论值是否改变都可用expression
字符串形式的指令表达式,如v-directiveName="1 + 1"
中,表达式为1 + 1
arg
传给指令的(可选)参数,如v-directiveName:argument
中,参数为argument
modifiers
一个包含修饰符的对象,如v-directiveName.foo.bar
中,修饰符对象为{ foo: true, bar: true }
def
指令使用的钩子函数
vnode
Vue 编译生成的虚拟节点oldVnode
上一个虚拟节点,仅在update
和componentUpdated
钩子中可用
Warning
使用指令时,如果要同时使用参数 arg
和修饰符 modifier
,应该按照先后次序使用(因为 arg
只有一个;而 modifier
可以串联多个,应该放在最后) v-directiveName:argument.modifier
Tip
动态指令参数:指令的参数是指 v-directiveName:param
中 param
部分,可以使用将参数设定为动态,通过 v-mydirective:[argument]="value"
的形式让指令支持动态参数,即允许使用者传递各种参数(而不进行预先限制),这使得自定义指令可以在应用中被灵活使用。
局部注册
在组件的选项 directives
中注册局部指令,也是提供 5 中钩子函数。然后可以在组件的模板中任何元素上使用自定义的指令 <tag v-directiveName="value">
// ...
directives: {
directiveName: {
// 设定钩子函数
inserted: function (el) {}
}
}