前言:
vue项目开发,大多数时间是在写业务代码,能用到的知识点无非是v-指令,mounted加载,props和emit传参,对这几个知识点熟悉的话,就可以完成大部分的开发工作,然而vue项目也并不是想象中这么简单,某些复杂功能会涉及到一些不常用的知识点,下面我将总结出来,以备不时之需。
1. 指令动态参数
模版中的绑定属性和绑定事件的方法如下:
<a v-bind:href="url">...</a>
<a v-on:click="doSomething">...</a>
对应的url和doSomething会在js中的data及methods中找到,这是常规的绑定方法。2.6.0版本后,vue新增了动态参数的绑定方法:
<a v-bind:[attributeName]="url"> ... </a>
<a v-on:[eventName]="doSomething"> ... </a>
以上可以看到参数名变成动态了,可以在js中的data中找到对应的参数名的值。
2. 计算属性、方法、侦听器
有些在计算属性中得到的结果,在方法里也能得到,那么计算属性和方法的区别在哪?官方文档中提到,“不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。”这里提到了一个名词,“响应式依赖”,读过文档中vue的响应式原理之后,不难理解,当data中定义的变量发生变化时,页面渲染会跟着变化,这个过程可以被理解为响应式,那么计算属性就是当data中的变量发生变化时,计算属性才会重新计算,否则保持原来的结果不变。而方法是只要执行一次,结果就会发生改变。
计算属性与侦听器在实现原理及功能上是及其接近的,官网给出的解释很简单,例子如下:
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
同样是计算fullName,计算属性的写法相对简单一些,而watch更适合执行一些开销较大的计算或者异步方法。
计算属性默认带的只有getter,但是当你需要时,可以手动设置一个setter,官方🌰如下:
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]
}
}
}
现在再运行 vm.fullName = 'John Doe'
时,setter 会被调用,vm.firstName
和 vm.lastName
也会相应地被更新。
3. 组件样式
这里主要讲class样式,当在一个自定义组件上使用 class
property 时,这些 class 将被添加到该组件的根元素上面。这个元素上已经存在的 class 不会被覆盖。
Vue.component('my-component', {
template: '<p class="foo bar">Hi</p>'
})
<my-component class="baz boo"></my-component>
<p class="foo bar baz boo">Hi</p>
对于带数据绑定 class 也同样适用:
<my-component v-bind:class="{ active: isActive }"></my-component>
当 isActive 为 truthy时,HTML 将被渲染成为:
<p class="foo bar active">Hi</p>
4.v-if和v-else
用法很简单,但有一个注意点,用key管理可复用的元素,官方🌰如下:
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>
上面代码,无论怎么切换loginType,如果输入框中有值,则值一直存在,不会因为切换就会消失,这时key的作用就来了:
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>
再次切换的时候,输入框中的值就消失了。
5. 列表渲染
列表渲染一般通过v-for来实现,比较简单,其中有几个关键点需要注意:
首先是要在循环的元素或组件上加key,key值一般是循环数据中的唯一值,目前元素的循环没有强制加key,但组件循环必须加key;
v-for中可以带有两个参数,第一个参数是数据项,第二个参数是当前项的索引,items是一个数组数据:
<ul id="example-2">
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
你也可以用 of
替代 in
作为分隔符
<div v-for="item of items"></div>
v-for还可以遍历一个对象:
<ul id="v-for-object" class="demo">
<li v-for="value in object">
{{ value }}
</li>
</ul>
new Vue({
el: '#v-for-object',
data: {
object: {
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
}
}
})
可以增加参数:name相当于对象数据中的key,value是值
<div v-for="(value, name) in object">
{{ name }}: {{ value }}
</div>
三个参数的情况:index为索引
<div v-for="(value, name, index) in object">
{{ index }}. {{ name }}: {{ value }}
</div>
6. 事件修饰符、按键修饰符、系统修饰键
只举几个官方🌰,详情阅读文档:
<!-- 阻止单击事件继续传播 -->
<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>
<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on
在监听键盘事件时添加按键修饰符:
<input v-on:keyup.enter="submit">
你可以直接将 KeyboardEvent.key
暴露的任意有效按键名转换为 kebab-case 来作为修饰符
<input v-on:keyup.page-down="onPageDown">
系统修饰键在这里包含ctrl alt shift meta这几个。即当进行某个操作时,需要同时按住键盘中的这几个键,才能触发。
<!-- Alt + C -->
<input v-on:keyup.alt.67="clear">
<!-- Ctrl + Click -->
<div v-on:click.ctrl="doSomething">Do something</div>
.exact
修饰符允许你控制由精确的系统修饰符组合触发的事件。
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button v-on:click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button v-on:click.exact="onClick">A</button>
7. 表单输入绑定v-model
v-model
在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用
value
property 和input
事件; - checkbox 和 radio 使用
checked
property 和change
事件; - select 字段将
value
作为 prop 并将change
作为事件
v-model的修饰符:
.lazy
在默认情况下,v-model
在每次 input
事件触发后将输入框的值与数据进行同步。你可以添加 lazy
修饰符,从而转为在 change
事件_之后_进行同步:
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg">
.number
如果想自动将用户的输入值转为数值类型,可以给 v-model
添加 number
修饰符:
<input v-model.number="age" type="number">
这通常很有用,因为即使在 type="number"
时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat()
解析,则会返回原始的值。
.trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model
添加 trim
修饰符:
<input v-model.trim="msg">
8. props向子组件传递数据
传固定值:
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
<blog-post title="My journey with Vue"></blog-post>
传绑定值:
new Vue({
el: '#blog-post-demo',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
})
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
></blog-post>
如果你想要将一个对象的所有 property 都作为 prop 传入,你可以使用不带参数的 v-bind
post: {
id: 1,
title: 'My Journey with Vue'
}
<blog-post v-bind="post"></blog-post>
等价于:
<blog-post
v-bind:id="post.id"
v-bind:title="post.title"
></blog-post>
props的大小写:在js里用camelCase(驼峰式),在html中用kebab-case
props的类型:
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
props是单项数据流
props验证:
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})
当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
非prop的Attribute
一个非 prop 的 attribute 是指传向一个组件,但是该组件并没有相应 prop 定义的 attribute。
假如一个<bootstrap-date-input>组件,其内部模板是<input type="date" class="form-control">,那么当在组件上加入一些属性后,<input type="date" class="form-control">中会自动加入该属性,并且会替换掉原来的属性。但class和style除外,这两种会与其进行合并,举例如下:
<bootstrap-date-input data-date-picker="activated" class="date-picker-theme-dark"></bootstrap-date-input>
<input type="date" data-date-picker="activated" class="form-control date-picker-theme-dark">
当然,上述这种模式也是可以被禁止的,
Vue.component('my-component', {
inheritAttrs: false,
// ...
})
被禁止后,组件上的attrebute就不会传到组件内的根元素了。
9. 监听子组件事件
<blog-post
...
v-on:enlarge-text="postFontSize += 0.1"
></blog-post>
监听组件blog-post中的事件enlarge-text,当触发这个事件时,在父组件中执行postFontSize+=0.1;
而这个事件是在组件blog-post内部进行触发的,假设该组件内部存在这样一个按钮:
<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button>
通过按钮的click事件,用$emit方法触发事件,从而传到父组件。
假如触发事件时需要带参数,那么写法如下:
<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
<blog-post
...
v-on:enlarge-text="postFontSize += $event"
></blog-post>
如果父级组件触发的是一个函数,那么写法如下:
<blog-post
...
v-on:enlarge-text="onEnlargeText"
></blog-post>
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
10. 在组件上使用v-model
实现原理:
<input v-model="searchText">
等价于
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
而在组件上使用v-model时,其等价于
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
组件内部的实现:
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
这样在组件上就可以使用:
<custom-input v-model="searchText"></custom-input>
如果input中type=checkbox,那么emit的事件就不是input,应该是change,参数变为$event.target.checked
11. .native的用法
假如想要监听某个组件根元素的原生事件,你可能会直接在组件中加入事件:
<base-input v-on:focus="onFocus"></base-input>
组件内部:
<input/>
再比如:
<myComponent @click="myClick"></myComponent>
组件内部:
<button>点我</button>
按我们以往对组件的理解,组件的自定义事件,是可以被组件内部的元素通过$emit来触发的,从而使父组件能够监听到组件内部的一些事件。但若想在组件上通过focus、click等原生事件来监听组件内部根元素的原生事件,就需要在组件的事件上加上.native才能生效,所以在上述代码中,需要在原生事件后加上.native,例如:
<base-input v-on:focus.native="onFocus"></base-input>
<myComponent @click.native="myClick"></myComponent>
.native虽然解决了组件根元素原生事件的监听问题,但有下面这种情况它是不能发挥作用的:
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
如上面代码,组件的根元素是label,不是input,而label是没有focus事件的,所以.native这个解决方案就不适合此种情况了,该如何解决呢?
12. $attrs和$listeners
通过理解$attrs和$listeners的用法,来看如何解决监听原生事件的问题。
首先来看$attrs,它代表的是组件传给其子元素的除props和class、style之外的所有属性,举个🌰:
<myComponent label="姓名" placeholder="请输入姓名" class="myInput" otherAttr="其它属性"></myComponent>
上面是一个组件,并传入了label、placeholder、class、otherAttr等属性,下面来看组件内部的实现:
<template>
<label>
{{label}}-
{{$attrs.placeholder}}-
<input v-bind="$attrs"/>
</label>
</template>
...
<script>
export default {
props:['label'],
mounted():{
console.log(this.$attrs);
}
}
</script>
打印的$attrs的值为{placeholder:"请输入姓名",otherAttr:"其它属性"},而lable和class没有被打印出来。此外需要注意的一点是,属性要用到哪个内部元素上,那么这个内部元素就要加上v-bind="$attrs"。
注意,默认情况下,非props属性会被直接继承到组件的根元素上,如果想阻止这种默认行为,需要在代码中加入inheritAttrs: false。
下面再来看一下$listeners
与$attrs类似,$listeners是将组件上的事件映射到组件内部的元素上,用法如下:
<base-input v-on:focus="onFocus"></base-input>
组件内部:
<label>
{{ label }}
<input
v-on="$listeners"
>
</label>
这样input上就带有focus事件了,当input获得焦点时,就会调用onFocus方法,这样就省去了通过$emit触发事件的步骤,这种方式在组件的嵌套中非常有用。(带有.native的事件不会被包含到$listeners中)
此外,元素上的原生事件可以覆盖组件上传入的事件,举例如下:
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
computed: {
inputListeners: function () {
var vm = this
// `Object.assign` 将所有的对象合并为一个新对象
return Object.assign({},
// 我们从父级添加所有的监听器
this.$listeners,
// 然后我们添加自定义监听器,
// 或覆写一些监听器的行为
{
// 这里确保组件配合 `v-model` 的工作
input: function (event) {
vm.$emit('input', event.target.value)
},
focus: function(){
console.log("我是元素上的focus")
}
}
)
}
},
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
</label>
`
})
inputListeners包含了$listeners中的事件,同时增加了input和focus两个事件,因为组件外部也传入了focus事件,所以这个后定义的focus事件会覆盖组件上的focus事件。
13. .sync修饰符
属性的传递是单项的,由上而下的,而组件如果想要改变组件外的属性,以前需要通过$emit触发一个监听事件,然后在对应的方法中改变属性的值,但是在使用.sync修饰符后就不用了。
<text-document v-bind:title.sync=oldTitle></text-document>
上面代码中,title是传入到组件内部的一个props,组件内部可以直接使用这个title,那么如果想在组件内部改变这个title的值,可以通过以下代码来实现:
this.$emit('update:title', newTitle)
update:xxxxx是固定写法。
其实.sync其实是一个语法糖,其等价于
<text-document
v-bind:title="oldTitle"
v-on:update:title="oldTitle= $event"
></text-document>
注意带有 .sync
修饰符的 v-bind
不能和表达式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’”
是无效的)。
14. 插槽slot
插槽的一般用法不难理解,主要看以下具名插槽和作用域插槽
具名插槽,看代码:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
上面是一个组件内部的元素,slot标签内加上了name属性。
<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>
上面是一个base-layout组件,组件中间的内容会被插入到组件内部的slot中。template中的v-slot的值,对应的就是组件内部的slot标签的name值。没有v-slot的内容,会被插入到没有name的slot内。(v-slot只能加在<template>上,只有一种情况除外,见后面内容);
上面代码渲染后的结果:
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>
具名插槽的名字,也可以是动态参数:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
具名插槽的简单写法:
<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>
作用域插槽稍微有点难理解,但也不是太难,直接看代码讲解:
<current-user>
{{ user.firstName }}
</current-user>
假如有一组件,组件中间是user.firstName,user的值不是在组件外获取的,而是来自组件内部,如下代码:
<template>
<span>
<slot>{{user.lastName}}</slot>
</span>
</template>
...
data(){
return {
user:{
firstName:"zhang",
lastName:"san"
}
}
}
这个时候代码是无法运行的,因为组件外部的user.firstName是取不到组件内部的user的。按照官方文档的说法,为了让 user
在父级的插槽内容中可用,我们可以将 user
作为 <slot>
元素的一个 attribute 绑定上去:
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
绑定在 <slot>
元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot
来定义我们提供的插槽 prop 的名字:
<current-user>
<template v-slot="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
上面的slotProps就是我们为组件内部的slot标签上的插槽prop起的名字,当然我们也可以起其它的你喜欢的名字。这样在组件外我们就可以调用组件内部的user数据了。
上面提到v-slot只能用在<template>标签中,但有一种情况例外,这种情况就是当组件内部只有一个默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot
直接用在组件上:
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>
更简单的写法是
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
</current-user>
但如果组件内部有其它具名插槽时,就不能这么写了。
上面用到的slotProps是我们为slot标签上的插槽prop起的名字,那么假如我们不起这个名字,还可以使用解构的方式来实现相同的功能:
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
还可以对prop进行重命名:
<current-user v-slot="{ user: person }">
{{ person.firstName }}
</current-user>
15. keep-alive
用于防止动态组件重复渲染,一般用在路由组件中比较多,这样路由在来回切换的时候,不用每一次都重新加载页面数据。需要注意的是,在使用keep-alive时,切换的动态组件需要有名字,也就是name。
16. 异步组件工厂函数
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})
以上结构是2.3.0新增的,还没有亲自使用过,暂且放这。
17. 混入
混入,一般用于各个组件可复用的部分,一个混入对象可以包含任意组件选项。
// 定义一个混入对象
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!"
混入对象和使用混入对象的组件,如果出现冲突时,以组件中内容为准;
同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
var mixin = {
created: function () {
console.log('混入对象的钩子被调用')
}
}
new Vue({
mixins: [mixin],
created: function () {
console.log('组件钩子被调用')
}
})
// => "混入对象的钩子被调用"
// => "组件钩子被调用"
18. 自定义指令
全局指令:
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
局部指令:
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
用法:
<input v-focus>
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
-
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 -
inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 -
update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 。
-
componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。 -
unbind
:只调用一次,指令与元素解绑时调用。
接下来我们来看一下钩子函数的参数 (即 el
、binding
、vnode
和 oldVnode
)。
指令钩子函数会被传入以下参数:
el
:指令所绑定的元素,可以用来直接操作 DOM。binding
:一个对象,包含以下 property:name
:指令名,不包括v-
前缀。value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
vnode
:Vue 编译生成的虚拟节点。oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。
除了 el
之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset
来进行。
这是一个使用了这些 property 的自定义钩子样例:
<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>
Vue.directive('demo', {
bind: function (el, binding, vnode) {
var s = JSON.stringify
el.innerHTML =
'name: ' + s(binding.name) + '<br>' +
'value: ' + s(binding.value) + '<br>' +
'expression: ' + s(binding.expression) + '<br>' +
'argument: ' + s(binding.arg) + '<br>' +
'modifiers: ' + s(binding.modifiers) + '<br>' +
'vnode keys: ' + Object.keys(vnode).join(', ')
}
})
new Vue({
el: '#hook-arguments-example',
data: {
message: 'hello!'
}
})
运行结果为:
name:'demo',
value:'hello',
expression:'message',
argument:'foo',
modifiers:{"a":true,"b":true},
vnode keys:tag,data,children...//省略若干,可见官网详情
动态指令参数:
直接看代码理解:
Vue.directive('pin', {
bind: function (el, binding, vnode) {
el.style.position = 'fixed'
var s = (binding.arg == 'left' ? 'left' : 'top')
el.style[s] = binding.value + 'px'
}
})
new Vue({
el: '#dynamicexample',
data: function () {
return {
direction: 'left'
}
}
})
<div id="dynamicexample">
<h3>Scroll down inside this section ↓</h3>
<p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
</div>
19. 参数h
import AnchoredHeading from './AnchoredHeading.vue'
new Vue({
el: '#demo',
render: function (h) {
return (
<AnchoredHeading level={1}>
<span>Hello</span> world!
</AnchoredHeading>
)
}
})
将 h
作为 createElement
的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的。从 Vue 的 Babel 插件的 3.4.0 版本开始,我们会在以 ES2015 语法声明的含有 JSX 的任何方法和 getter 中 (不是函数或箭头函数中) 自动注入 const h = this.$createElement
,这样你就可以去掉 (h)
参数了。对于更早版本的插件,如果 h
在当前作用域中不可用,应用会抛错。
20. 插件
使用插件很简单Vue.use();难的是开发插件,这部分内容需要查看官方文档的插件写法,同时到网上找一些插件的例子。
21. 过滤器
<!-- 在双花括号中 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
你可以在一个组件的选项中定义本地的过滤器:
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
或者在创建 Vue 实例之前全局定义过滤器:
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
new Vue({
// ...
})
过滤器可以串联:
{{ message | filterA | filterB }}
22. 过渡&动画
基本例子:
<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
new Vue({
el: '#demo',
data: {
show: true
}
})
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
例子说明:
在进入/离开的过渡中,会有 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
被删除),在过渡/动画完成之后移除。
对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>
,则 v-
是这些类名的默认前缀。如果你使用了 <transition name="my-transition">
,那么 v-enter
会替换为 my-transition-enter
。
自定义过渡类名:
enter-class
enter-active-class
enter-to-class
(2.1.8+)leave-class
leave-active-class
leave-to-class
(2.1.8+)
配合第三方 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>
new Vue({
el: '#example-3',
data: {
show: true
}
})
显性的过渡时间
<transition :duration="1000">...</transition>//单位是毫秒
<transition :duration="{ enter: 500, leave: 800 }">...</transition>
过渡模式
-
in-out
:新元素先进行过渡,完成之后当前元素过渡离开。 -
out-in
:当前元素先进行过渡,完成之后新元素过渡进入。
<transition name="fade" mode="out-in">
<!-- ... the buttons ... -->
</transition>
列表过渡
<transition-group>组件使用要点:
- 不同于
<transition>
,它会以一个真实元素呈现:默认为一个<span>
。你也可以通过tag
attribute 更换为其他元素。 - 过渡模式不可用,因为我们不再相互切换特有的元素。
- 内部元素总是需要提供唯一的
key
attribute 值。 - CSS 过渡的类将会应用在内部的元素中,而不是这个组/容器本身。
列表的排序过渡
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
<div id="list-complete-demo" class="demo">
<button v-on:click="shuffle">Shuffle</button>
<button v-on:click="add">Add</button>
<button v-on:click="remove">Remove</button>
<transition-group name="list-complete" tag="p">
<span
v-for="item in items"
v-bind:key="item"
class="list-complete-item"
>
{{ item }}
</span>
</transition-group>
</div>
new Vue({
el: '#list-complete-demo',
data: {
items: [1,2,3,4,5,6,7,8,9],
nextNum: 10
},
methods: {
randomIndex: function () {
return Math.floor(Math.random() * this.items.length)
},
add: function () {
this.items.splice(this.randomIndex(), 0, this.nextNum++)
},
remove: function () {
this.items.splice(this.randomIndex(), 1)
},
shuffle: function () {//.shuffle函数,是lodash中的一个函数,创建一个被打乱值的集合。
this.items = _.shuffle(this.items)
}
}
})
.list-complete-item {
transition: all 1s;
display: inline-block;
margin-right: 10px;
}
.list-complete-enter, .list-complete-leave-to
/* .list-complete-leave-active for below version 2.1.8 */ {
opacity: 0;
transform: translateY(30px);
}
.list-complete-leave-active {
position: absolute;
}
列表的其它过渡方式,详见官网,这里引用官网一句话:唯一的限制是你的想象力。
状态过渡:详见官网