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 + 1arg传给指令的(可选)参数,如v-directiveName:argument中,参数为argumentmodifiers一个包含修饰符的对象,如v-directiveName.foo.bar中,修饰符对象为{ foo: true, bar: true }def指令使用的钩子函数
vnodeVue 编译生成的虚拟节点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) {}
}
}