五一闲在家没事做,又重温了一下vue
的入门指南,边看边记录,便于记忆。
1.安装
1.1 直接<script>
引入。
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
1.2 npm
npm install vue
1.3 CLI
2.介绍
Vue
是一套用于构建用户界面的渐进式框架,支持申明式渲染,条件与循环,处理用户输入,组件化应用构建等。
3.Vue实例
一个Vue
应用由一个通过new Vue
创建的根vue
,以及可选的嵌套组件和可选的组件树组成,所有的vue
组件都是vue
实例。
var vm = new Vue({
// 选项
})
3.1.数据与方法
当一个Vue
实例被创建时,它将data
对象中所有的property
加入到Vue
的响应式系统中,当这些property
的值发生改变时,视图将产生响应,更新为最新的值。
// 我们的数据对象
var data = { a: 1 }
// 该对象被加入到一个 Vue 实例中
var vm = new Vue({
data: data
})
// 获得这个实例上的 property
// 返回源数据中对应的字段
vm.a == data.a // => true
注意
只有当实例被创建时就已经存在于data
中的property
才是响应式的。
除了用户自定义的data
数据外,vue
实例还提供了一些自身的property
和方法,它们都有前缀$
。
var data = { a: 1 }
var vm = new Vue({
el: '#example',
data: data
})
vm.$data === data // => true
vm.$el === document.getElementById('example') // => true
// $watch 是一个实例方法
vm.$watch('a', function (newValue, oldValue) {
// 这个回调将在 `vm.a` 改变后调用
})
3.1.生命周期
每个Vue
实例在被创建时都要经过一系列的初始化过程,比如设置数据监听,编译模板,挂载DOM,监听数据更新等等,这些生命周期的函数给了用户在不同阶段添加自己的代码的机会。生命周期钩子的this
上下文指向调用它的vue
实例。
4.模板语法
Vue
使用了基于html
的模板语法,允许开发者声明式的将DOM
绑定至底层Vue
实例的数据,所有Vue
的模板都是合法的HTML
,所以能被规范的浏览器和HTML
解析器解析。在底层的实现上,Vue
将模板编译成虚拟DOM
渲染函数,结合响应系统,Vue
能够智能的计算出最少需要重新渲染多少组件,并把DOM
操作的次数减少到最少。
4.1.文本
使用{{}}
可以方便的显示文本信息,如下所示:
<span>Message: {{ msg }}</span>
当msg
的值发生变化时,界面上也将发生相应的变化 。
4.2.原始html
如果需要在界面上渲染html
内容,需要使用v-html
指令:
<span v-html="rawHtml"></span>
4.3.Attribute
使用v-bind
可以动态设置attribute
的值。
<button v-bind:disabled="isButtonDisabled">Button</button>
4.4.表达式
在vue
的模板中,除了使用data
中设置的property
外,还可以使用表达式的形势:
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
4.5.指令
指令是带有v-
前缘的特殊attribute
,指令的职责是:当表达式的值改变时,将其产生的连带影响,响应式的作用于DOM
。
<p v-if="seen">现在你看到我了</p>
这里,v-if
指令将根据表达式 seen
的值的真假来插入/移除 <p>
元素。
4.6.动态参数
从2.6.0
开始,可以用广括号括起来的javascript
表达式作为一个指令的参数,可以是attribute
和事件名
:
<a v-bind:[attributeName]="url"> ... </a>
<a v-on:[eventName]="doSomething"> ... </a>
4.7.修饰符
修饰符是以半角句号.
指明的特殊后缀,用于指出一个指令应该以特殊方式绑定,例如,.prevent
修饰符告诉 v-on
指令对于触发的事件调用 event.preventDefault()
:
<form v-on:submit.prevent="onSubmit">...</form>
4.8.缩写
对于一些频繁使用的指令,使用缩写是很必要的,因此Vue
为 v-bind
和 v-on
这两个最常用的指令,提供了特定简写:
v-bind
<!-- 完整语法 -->
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a>
v-on
<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>
<!-- 缩写 -->
<a @click="doSomething">...</a>
5.计算属性和侦听器
5.1 计算属性
模板内只能写一些简单的表达式,来进行简单的运算,但当逻辑较复杂时,让模板难以维护,所以,对于任何复杂逻辑,都应当使用计算属性,不仅能简化逻辑,还能提升代码的复用。
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
}
}
})
这里我们声明了一个计算属性 reversedMessage
。我们提供的函数将用作 property
vm.reversedMessage
的 getter
函数:
computed与methods区别
我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。
5.2侦听属性
Vue
提供了一种更通用的方式来观察和响应Vue
实例上的数据变动:侦听属性。通过 watch
选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。在watch
中定义需要监听的property
,当属性发生变化时,将执行对应的方法:
<div id="demo">{{ fullName }}</div>
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
6. Class 与 Style绑定
在操作元素的class
列表和内联样式时,可以使用v-bind
来处理它们,只需要通过表达式计算出字符串结果即可。
6.1.绑定HTML Class
6.1.1 对象语法
可以给class
传递一个对象,来动态的切换class
:
<div
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>
绑定的data
如下所示:
data: {
isActive: true,
hasError: false
}
渲染结果如下所示:
<div class="static active"></div>
当 isActive
或者 hasError
变化时,class
列表将相应地更新。例如,如果 hasError
的值为 true
,class
列表将变为 "static active text-danger"
。
绑定的数据对象不必内联定义在模板里:
<div v-bind:class="classObject"></div>
data: {
classObject: {
active: true,
'text-danger': false
}
}
或者使用计算属性:
data: {
isActive: true,
error: null
},
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
6.1.2 数组语法
我们可以把一个数组传给v-bind:class
,以应用一个class
列表:
<div v-bind:class="[activeClass, errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
渲染结果为:
<div class="active text-danger"></div>
6.2.绑定内联样式
6.2.1 对象语法
v-bind:style
的对象语法十分直观——看着非常像 CSS
,但其实是一个 JavaScript
对象。CSS
property
名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名:
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}
6.2.2.数组语法
v-bind:style
的数组语法可以将多个样式对象应用到同一个元素上:
<div v-bind:style="[baseStyles, overridingStyles]"></div>
7.条件渲染
7.1. v-if
v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。v-if
必须将它添加到一个元素上,除了作用于html
中的元素外,还可以在template
中使用:
<h1 v-if="ok">Vue is awesome!</h1>
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
7.2. v-else
可以使用 v-else
指令来表示 v-if
的else
块:
<div v-if="Math.random() > 0.5">
Now you see me
</div>
<div v-else>
Now you don't
</div>
v-else
元素必须紧跟在带 v-if
或者 v-else-if
的元素的后面,否则它将不会被识别。
7.3.v-else-if
v-else-if
,可以充当 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>
7.4.用 key 管理可复用的元素
Vue
会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做能使 Vue
变得非常快。但有时候为了防止元素复用,需要使用key
属性。比如在使用v-for
指令的时候,经常用传到:
<ul id="example-1">
<li v-for="item in items" :key="item.message">
{{ item.message }}
</li>
</ul>
7.5. v-show
另一个用于根据条件展示元素的选项是 v-show
指令。用法大致一样:
<h1 v-show="ok">Hello!</h1>
不同的是带有 v-show
的元素始终会被渲染并保留在 DOM
中。v-show
只是简单地切换元素的 CSS property display
。v-show
不支持 <template>
元素,也不支持 v-else
7.6 v-if与v-show对比
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS
进行切换。
一般来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show
较好;如果在运行时条件很少改变,则使用 v-if
较好。
8.列表渲染
8.1.v-for
可以使用v-for
指令基于一个数组或者对象来渲染一个列表,v-for
指令需要使用(item ,index) in items
形势的特殊语法,其中items
是源数据数组或对象,而item
是被迭代的数组元素。index
为元素的索引值 。index
可以省略,如:item in items
。如果items
为对象,那第二个参数index
为对象的key
。
<ul id="example-1">
<li v-for="item in items" :key="item.message">
{{ item.message }}
</li>
</ul>
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
也可以用 of
替代 in
作为分隔符,因为它更接近 JavaScript
迭代器的语法:
<div v-for="item of items"></div>
8.2 数组更新检测
Vue
将被侦听的数组的变更方法进行了包裹,所以它们也将会触发到视图更新,这些被包裹过的方法如下所示:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
如果使用filter()
、concat()
和 slice()
这些非变更的方法,执行这些方法后不会变更原始数组,会返回一个新数组,可以将新数组替换成旧数据,同样可以起到变更视图的作用。
9. 事件处理
9.1. 监听事件
可以使用v-on
指令监听DOM
事件,并在触发时运行一些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>
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})
9.2.事件处理方法
除了在v-on
指令中使用表达式外,还可以使用方法,如果在方法中需要传递DOM
事件,可以传递$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)
}
}
9.3.事件修饰符
在事件处理程序中调用 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>
使用修饰符时,顺序很重要,相应的代码会以同样的顺序产生,因此,用
v-on:click.prevent.self
会阻止所有的点击,而v-on:click.self.prevent
只会阻止对元素自身的点击。
9.4.按键修饰符
在监听键盘事件时,我们经常需要检查详细的按键。Vue
允许为 v-on
在监听键盘事件时添加按键修饰符:
<input v-on:keyup.enter="submit">
也可以直接将 KeyboardEvent.key
暴露的任意有效按键名转换为 kebab-case
来作为修饰符。
<input v-on:keyup.page-down="onPageDown">
10. 表单输入绑定
10.1.基础用法
在Vue
中,可以使用v-model
指令在表单<input>
,<textarea>
,<select>
元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。v-model
负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model
会忽略所有表单元素的value
、checked
、selected
attribute
的初始值而总是将Vue
实例的数据作为数据来源。你应该通过JavaScript
在组件的data
选项中声明初始值。
v-model
在内部为不同的输入元素使用不同的property
并抛出不同的事件:
text
和textarea
元素使用value
property
和input
事件;checkbox
和radio
使用checked
property
和change
事件;select
字段将value
作为prop
并将change
作为事件。
10.2.修饰符
10.2.1 .lazy
在默认情况下,v-model
在每次input
事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy
修饰符,从而转为在 change
事件之后进行同步:
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg">
10.2.2 .number
如果想自动将用户的输入值转为数值类型,可以给 v-model
添加 number
修饰符:
<input v-model.number="age" type="number">
这通常很有用,因为即使在 type="number"
时,HTML
输入元素的值也总会返回字符串。如果这个值无法被 parseFloat()
解析,则会返回原始的值。
10.2.3 .trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model
添加 trim
修饰符:
<input v-model.trim="msg">
11.组件
组件是可以复用的Vue
实例,与 new Vue
接收相同的选项,例如 data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像 el
这样根实例特有的选项。
11.1组件注册
在注册一个组件的时候,始终需要给它一个名字,组件命名时强烈推荐遵循 W3C
规范中的自定义组件名 (字母全小写且必须包含一个连字符)。这会帮助你避免和当前以及未来的 HTML
元素相冲突。
11.1.1全部注册
全局注册的组件可以用在其被注册之后的任何 (通过 new Vue
) 新创建的 Vue
根实例,也包括其组件树中的所有子组件的模板中。
Vue.component('my-component-name', {
// ... options ...
})
11.1.2局部注册
全局注册往往是不够理想的。比如,如果你使用一个像 webpack
这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript
的无谓的增加。
在这些情况下,你可以通过一个普通的 JavaScript
对象来定义组件:
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }
然后在 components 选项中定义想要使用的组件:
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
对于 components
对象中的每个 property
来说,其 property
名就是自定义元素的名字,其 property
值就是这个组件的选项对象。
11.2. Prop
使用prop
可以为组件添加属性,通过引用组件时从父组件传递参数到子组件。在给prop
定义名称时可以使用驼峰命名法,camelCase
(驼峰命名法) 的 prop
名需要使用其等价的 kebab-case
(短横线分隔命名) 命名:
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>
11.2.1 Prop类型
在没有定义类型时,默认使用字符串数组的形式:
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
但是,通常你希望每个 prop
都有指定的值类型。这时,可以以对象形式列出 prop
,这些 property
的名称和值分别是 prop
各自的名称和类型:
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise
}
11.2.2 传递静态或动态 Prop
可以给 prop
传入一个静态的值:
<blog-post title="My journey with Vue"></blog-post>
也可以通过 v-bind
动态赋值,例如:
<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>
<!-- 动态赋予一个复杂表达式的值 -->
<blog-post
v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>
注:当传递数字,布尔值,数组,对象时,无论是静态还是动态,都得会用动态绑定的方式:
<!-- 即便 `42` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:likes="42"></blog-post>
<!-- 包含该 prop 没有值的情况在内,都意味着 `true`。-->
<blog-post is-published></blog-post>
<!-- 即便 `false` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:is-published="false"></blog-post>
<!-- 即便数组是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:author="post.author"></blog-post>
11.3.单向数据流
所有的 prop
都使得其父子 prop
之间形成了一个单向下行绑定:父级 prop
的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生变更时,子组件中所有的 prop
都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop
。如果你这样做了,Vue
会在浏览器的控制台中发出警告。
注意在
JavaScript
中对象和数组是通过引用传入的,所以对于一个数组或对象类型的prop
来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。
11.4. Prop验证
可以为组件的 prop
指定验证要求,例如你知道的这些类型。如果有一个需求没有被满足,则 Vue
会在浏览器控制台中警告你。这在开发一个会被别人用到的组件时尤其有帮助。
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
11.4.1.默认值
在定义prop
的时候,可以使用default
指定默认值:
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
11.4.2.类型检查
type
可以是下列原生构造函数中的一个:
String
Number
Boolean
Array
Object
Date
Function
Symbol
11.4.3.必填项
使用required
可以设置属性是否必填。
// 必填的字符串
propC: {
type: String,
required: true
},
11.5.自定义事件
11.5.1 事件名
不同于组件和 prop
,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。推荐使用 kebab-case
的事件名。
<my-component v-on:my-event="doSomething"></my-component>
采用如下所示的方式调用。
this.$emit('my-event')
11.5.2. 自定义组件的v-model
一个组件上的 v-model
默认会利用名为 value
的 prop
和名为 input
的事件,但是像单选框、复选框等类型的输入控件可能会将 value
attribute
用于不同的目的。model
选项可以用来避免这样的冲突:
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
现在在这个组件上使用 v-model
的时候:
<base-checkbox v-model="lovingVue"></base-checkbox>
11.5.3. sync修饰符
在有些情况下,我们可能需要对一个 prop
进双向绑定。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以变更父组件,且在父组件和子组件都没有明显的变更来源。
这也是为什么我们推荐以 update:myPropName
的模式触发事件取而代之。举个例子,在一个包含 title
prop
的假设的组件中,我们可以用以下方法表达对其赋新值的意图:
this.$emit('update:title', newTitle)
然后父组件可以监听那个事件并根据需要更新一个本地的数据 property
。例如:
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
为了方便起见,我们为这种模式提供一个缩写,即 .sync
修饰符:
<text-document v-bind:title.sync="doc.title"></text-document>
12.插槽
12.1.插槽内容
Vue
实现了一套内容分发的 API
,这套 API
的设计灵感源自 Web Components 规范草案
,将 <slot>
元素作为承载分发内容的出口。
它允许如下所示的方式合成组件:
<navigation-link url="/profile">
Your Profile
</navigation-link>
然后你在 <navigation-link>
的模板中可能会写为:
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>
当组件渲染的时候,<slot></slot>
将会被替换为“Your Profile”
。插槽内可以包含任何模板代码,包括 HTML
或者其它组件:
<navigation-link url="/profile">
<!-- 添加一个图标的组件 -->
<font-awesome-icon name="user"></font-awesome-icon>
Your Profile
</navigation-link>
如果 <navigation-link>
没有包含一个 <slot>
元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
12.2.后备内容
有时为一个插槽设置具体的后备(也就是默认的)内容是很有用的,它只有在没有提供内容的时候被渲染,例如在一个 <submit-button>
组件中:
<button type="submit">
<slot></slot>
</button>
我们可能希望这个 <button>
内绝大多数情况下都渲染文本“Submit”
。为了将“Submit”
作为后备内容,我们可以将它放在 <slot>
标签内:
<button type="submit">
<slot>Submit</slot>
</button>
现在当我在一个父级组件中使用 <submit-button>
并且不提供任何插槽内容时:
<submit-button></submit-button>
后备内容“Submit”将会被渲染:
<button type="submit">
Submit
</button>
但是如果我们提供内容:
<submit-button>
Save
</submit-button>
则这个提供的内容将会被渲染从而取代后备内容:
<button type="submit">
Save
</button>
12.3.具名插槽
有时我们需要多个插槽。例如对于一个带有如下模板的 <base-layout>
组件:
<div class="container">
<header>
<!-- 我们希望把页头放这里 -->
</header>
<main>
<!-- 我们希望把主要内容放这里 -->
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
</footer>
</div>
对于这样的情况,<slot>
元素有一个特殊的 attribute:name
。这个 attribute
可以用来定义额外的插槽:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
一个不带 name
的 <slot>
出口会带有隐含的名字“default”
。
在向具名插槽提供内容的时候,我们可以在一个 <template>
元素上使用 v-slot
指令,并以 v-slot
的参数的形式提供其名称:
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
12.4.作用域插槽
有时让插槽内容能够访问子组件中才有的数据是很有用的。例如,设想一个带有如下模板的 <current-user>
组件:
<span>
<slot>{{ user.lastName }}</slot>
</span>
我们可能想换掉备用内容,用名而非姓来显示。如下:
<current-user>
{{ user.firstName }}
</current-user>
然而上述代码不会正常工作,因为只有 <current-user>
组件可以访问到 user
而我们提供的内容是在父级渲染的。
为了让 user
在父级的插槽内容中可用,我们可以将 user
作为 <slot>
元素的一个 attribute
绑定上去:
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
12.5.具名插槽缩写
跟 v-on
和 v-bind
一样,v-slot
也有缩写,即把参数之前的所有内容 (v-slot:)
替换为字符 #
。例如 v-slot:header
可以被重写为 #header
:
<base-layout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
13.动态组件与异步组件
13.1 动态组件
在vue
中,可以使用 is
attribute
来切换不同的组件:
<component v-bind:is="currentTabComponent"></component>
可以使用<keep-alive>
来防止组件重复创建:
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>
13.2.异步组件
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue
允许以一个工厂函数的方式定义组件,这个工厂函数会异步解析自定义的组件。Vue
只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。例如:
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
14.处理边界情况
14.1. 访问元素&组件
在绝大多数情况下,我们最好不要触达另一个组件实例内部或手动操作 DOM 元素。不过也确实在一些情况下做这些事情是合适的。
14.1.1.访问根实例
在每个 new vue
实例的子组件中,其根实例可以通过$root
property
进行访问,例如,有下面这样的根实例:
// Vue 根实例
new Vue({
data: {
foo: 1
},
computed: {
bar: function () { /* ... */ }
},
methods: {
baz: function () { /* ... */ }
}
})
所有的子组件都可以将这个实例作为一个全局 store
来访问或使用。
// 获取根组件的数据
this.$root.foo
// 写入根组件的数据
this.$root.foo = 2
// 访问根组件的计算属性
this.$root.bar
// 调用根组件的方法
this.$root.baz()
14.1.2.访问父级组件实例
和$root
类似,$parent
property
可以用来从一个子组件访问父组件的实例,它提供了一种机会,可以在后期随时触达父级组件,以替代将数据以 prop
的方式传入子组件的方式。
14.2. 访问子组件实例或子元素
尽管存在 prop
和事件,有的时候你仍可能需要在 JavaScript
里直接访问一个子组件。为了达到这个目的,可以通过 ref
这个 attribute
为子组件赋予一个 ID
引用。例如:
<base-input ref="usernameInput"></base-input>
定义好ref
后,可以使用如下的访问来获取:
this.$refs.usernameInput
$refs
只会在组件渲染完成之后才生效,并且它们不是响应式的,这仅作为一个用于直接操作子组件“逃生舱”——你应该避免在模板或计算属性中访问$refs
。
14.3.依赖注入
当使用$parent
property
无法很好的扩展到理深层级的嵌套组件上时,就得使用依赖注入,它用到了两个新的实例选项:provide
和inject
。
provide
选项允许我们指定我们想要提供给后台组件的数据或方法。比如在map
组件中,提供一个getMap
的方法,方便子组件
能调用getMap
方法:
provide: function () {
return {
getMap: this.getMap
}
}
然后在任何后代组件里,我们都可以使用 inject
选项来接收指定的我们想要添加在这个实例上的 property
:
inject: ['getMap']
14.4. 程序化的事件侦听器
现在,已经知道了 $emit
的用法,它可以被 v-on
侦听,但是 Vue
实例同时在其事件接口中提供了其它的方法。我们可以:
通过
$on(eventName, eventHandler)
侦听一个事件
通过$once(eventName, eventHandler)
一次性侦听一个事件
通过$off(eventName, eventHandler)
停止侦听一个事件
mounted: function () {
this.attachDatepicker('startDateInput')
this.attachDatepicker('endDateInput')
},
methods: {
attachDatepicker: function (refName) {
var picker = new Pikaday({
field: this.$refs[refName],
format: 'YYYY-MM-DD'
})
this.$once('hook:beforeDestroy', function () {
picker.destroy()
})
}
}
14.5.强制更新
可以使用$forceUpdate
来强制更新。
15.动画
Vue
在插入、更新或者移除 DOM
时,提供多种不同方式的应用过渡效果。包括以下工具:
- 在
CSS
过渡和动画中自动应用class
- 可以配合使用第三方
CSS
动画库,如Animate.css
- 在过渡钩子函数中使用
JavaScript
直接操作DOM
- 可以配合使用第三方
JavaScript
动画库,如Velocity.js
15.1.单元素/组件的过滤
Vue
提供了transition
的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过滤
- 条件渲染 (使用
v-if
) - 条件展示 (使用
v-show
) - 动态组件
- 组件根节点
当插入或删除包含在transition
组件中的元素时,Vue
将会做以下处理: - 自动嗅探目标元素是否应用了
CSS
过渡或动画,如果是,在恰当的时机添加/删除CSS
类名。 - 如果过渡组件提供了
JavaScript
钩子函数,这些钩子函数将在恰当的时机被调用。 - 如果没有找到
JavaScript
钩子并且也没有检测到CSS
过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行。(注意:此指浏览器逐帧动画机制,和
Vue的
nextTick` 概念不同)。
15.1.1 过渡的类名
在进入/离开的过渡中,会有 6 个 class
切换。
-
v-enter
:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。 -
v-enter-active
:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。 -
v-enter-to
:2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时v-enter
被移除),在过渡/动画完成之后移除。 -
v-leave
:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。 -
v-leave-active
:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。 -
v-leave-to
:2.1.8 版及以上定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时v-leave
被删除),在过渡/动画完成之后移除。
15.1.2. CSS过渡
常用的过渡都是使用CSS
过渡。
<div id="example-1">
<button @click="show = !show">
Toggle render
</button>
<transition name="slide-fade">
<p v-if="show">hello</p>
</transition>
</div>
new Vue({
el: '#example-1',
data: {
show: true
}
})
/* 可以设置不同的进入和离开动画 */
/* 设置持续时间和动画函数 */
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to
/* .slide-fade-leave-active for below version 2.1.8 */ {
transform: translateX(10px);
opacity: 0;
}
15.1.3 自定义过渡的类名
可以通过以下 attribute
来自定义过渡类名:
enter-class
enter-active-class
enter-to-class (2.1.8+)
leave-class
leave-active-class
-
leave-to-class (2.1.8+)
他们的优先级高于普通的类名,这对于Vue
的过渡系统和其他第三方CSS
动画库,如Animate.css
结合使用十分有用。
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">
<div id="example-3">
<button @click="show = !show">
Toggle render
</button>
<transition
name="custom-classes-transition"
enter-active-class="animated tada"
leave-active-class="animated bounceOutRight"
>
<p v-if="show">hello</p>
</transition>
</div>
15.1.4. 显性的过渡持续时间
可以用 <transition>
组件上的 duration
prop
定制一个显性的过渡持续时间 (以毫秒计):
<transition :duration="1000">...</transition>
也可以定制进入和移出的持续时间:
<transition :duration="{ enter: 500, leave: 800 }">...</transition>
15.1.5. JavaScript钩子
可以在 attribute
中声明 JavaScript
钩子。一个使用 Velocity.js
的简单例子:
<!--
Velocity 和 jQuery.animate 的工作方式类似,也是用来实现 JavaScript 动画的一个很棒的选择
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
<div id="example-4">
<button @click="show = !show">
Toggle
</button>
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
v-bind:css="false"
>
<p v-if="show">
Demo
</p>
</transition>
</div>
new Vue({
el: '#example-4',
data: {
show: false
},
methods: {
beforeEnter: function (el) {
el.style.opacity = 0
el.style.transformOrigin = 'left'
},
enter: function (el, done) {
Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 })
Velocity(el, { fontSize: '1em' }, { complete: done })
},
leave: function (el, done) {
Velocity(el, { translateX: '15px', rotateZ: '50deg' }, { duration: 600 })
Velocity(el, { rotateZ: '100deg' }, { loop: 2 })
Velocity(el, {
rotateZ: '45deg',
translateY: '30px',
translateX: '30px',
opacity: 0
}, { complete: done })
}
}
})
15.2 初始渲染的过渡
可以通过 appear
attribute
设置节点在初始渲染的过渡。
<transition appear>
<!-- ... -->
</transition>
15.3 过渡模式
Vue
提供了过渡模式:
-
in-out
:新元素先进行过渡,完成之后当前元素过渡离开。 -
out-in
:当前元素先进行过渡,完成之后新元素过渡进入。
<transition name="fade" mode="out-in">
<!-- ... the buttons ... -->
</transition>
15.4 多个组件的过渡
多个组件的过渡简单很多 - 不需要使用 key
attribute
。相反,只需要使用动态组件:
<transition name="component-fade" mode="out-in">
<component v-bind:is="view"></component>
</transition>
new Vue({
el: '#transition-components-demo',
data: {
view: 'v-a'
},
components: {
'v-a': {
template: '<div>Component A</div>'
},
'v-b': {
template: '<div>Component B</div>'
}
}
})
15.5 列表过渡
使用 <transition-group>
组件可以进行列表过渡:
<div id="list-demo" class="demo">
<button v-on:click="add">Add</button>
<button v-on:click="remove">Remove</button>
<transition-group name="list" tag="p">
<span v-for="item in items" v-bind:key="item" class="list-item">
{{ item }}
</span>
</transition-group>
</div>
16.混入
混入(mixin
)提供了一种非常灵活的方式,来分发Vue
组件中可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
// 定义一个混入对象
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 定义一个使用混入对象的组件
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"
16.1. 选项合并
当组件和混入对象含有同名选项时,遵循下面的合并规则:
- 数据对象中具有同名的选项时,以组件的数据选项优先。
- 同名钩子函数合并为一个数组,先执行混入对象中的钩子函数。
- 值为对象选项时,比如
methods
,components
,directives
,将被合并为同一个对象,当有同名的对象键名时,取组件对象的键值对。
16.2 全局混入
混入也可以进行全局注册。使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue
实例。使用恰当时,这可以用来为自定义选项注入处理逻辑。
// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
new Vue({
myOption: 'hello!'
})
// => "hello!"
16.3. 自定义选项合并策略
自定义选项将使用默认策略,即简单地覆盖已有值。如果想让自定义选项以自定义逻辑合并,可以向 Vue.config.optionMergeStrategies
添加一个函数:
Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {
// 返回合并后的值
}
17.自定义指令
除了核心功能默认内置的指令 (v-model
和 v-show
),Vue
也允许注册自定义指令。注意,在 Vue2.0
中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM
元素进行底层操作,这时候就会用到自定义指令。举个聚焦输入框的例子,如下:
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
然后可以在模板中任何元素上使用新的 v-focus
property
,如下:
<input v-focus>
17.1.钩子函数
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
-
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 -
inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 -
update
:所在组件的 VNode 更新时调用,但是可能发生在其子VNode
更新之前。指令的值可能发生了改变,也可能没有。 -
componentUpdated
:指令所在组件的VNode
及其子VNode
全部更新后调用。 -
unbind
:只调用一次,指令与元素解绑时调用。
17.1.1.钩子函数参数
指令钩子函数会被传入以下参数:
-
el
:指令所绑定的元素,可以用来直接操作DOM
。 -
binding
:一个对象,包含以下name
,value
,oldValue
,expression
,arg
,modifiers
等属性。 -
vnode
:Vue
编译生成的虚拟节点。 -
oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。
除了
el
之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的dataset
来进行。
18.渲染函数与JSX
Vue
推荐在绝大多数情况下使用模板来创建 HTML
。然而在一些场景中,真的需要 JavaScript
的完全编程的能力。这时可以用渲染函数,它比模板更接近编译器。
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // 标签名称
this.$slots.default // 子节点数组
)
},
props: {
level: {
type: Number,
required: true
}
}
})
18.1. 虚拟DOM
Vue
通过建立一个虚拟DOM来追踪自己要如何改变真实DOM
:
// @returns {VNode}
createElement(
// {String | Object | Function}
// 一个 HTML 标签名、组件选项对象,或者
// resolve 了上述任何一种的一个 async 函数。必填项。
'div',
// {Object}
// 一个与模板中 attribute 对应的数据对象。可选。
{
// (详情见下一节)
},
// {String | Array}
// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
// 也可以使用字符串来生成“文本虚拟节点”。可选。
[
'先写一些文字',
createElement('h1', '一则头条'),
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
19.插件
插件通常用来为 Vue
添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:
- 添加全局方法或者
property
。 - 添加全局资源:指令/过滤器/过渡等。
- 通过全局混入来添加一些组件选项。
- 添加
Vue
实例方法,通过把它们添加到Vue.prototype
上实现。 - 一个库,提供自己的
API
,同时提供上面提到的一个或多个功能。
19.1.使用插件
通过全局方法 Vue.use()
使用插件。它需要在你调用 new Vue()
启动应用之前完成:
Vue.use(MyPlugin, { someOption: true })
19.2.开发插件
Vue.js
的插件应该暴露一个 install
方法。这个方法的第一个参数是 Vue
构造器,第二个参数是一个可选的选项对象:
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或 property
Vue.myGlobalMethod = function () {
// 逻辑...
}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})
// 3. 注入组件选项
Vue.mixin({
created: function () {
// 逻辑...
}
...
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}
20.过滤器
Vue.js
允许自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind
表达式 。过滤器应该被添加在 JavaScript
表达式的尾部,由“管道”符号指示:
<!-- 在双花括号中 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
20.1.局部过滤器
在一个组件的选项中定义局部的过滤器:
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
20.2.全局过滤器
在创建 Vue 实例之前全局定义过滤器:
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
new Vue({
// ...
})
注意:当全局过滤器和局部过滤器重名时,会采用局部过滤器。
20.3.参数
过滤器是 JavaScript
函数,因此可以接收参数:
{{ message | filterA('arg1', arg2) }}
这里,filterA
被定义为接收三个参数的过滤器函数。其中 message
的值作为第一个参数,普通字符串 'arg1'
作为第二个参数,表达式 arg2
的值作为第三个参数。
网友评论