每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始的:
<script>
// Vue实例
new Vue({ //创建一个Vue的实例
el: "#app", //挂载点是id="app"的地方
data: { //数据
ditu:true,
wangge:false
},
methods:{
toggleWangge:function(){
this.wangge = !this.wangge;
if(this.wangge == true){
this.wangText = "隐藏网格";
this.$refs.abc.style.backgroundColor = "#262C30";
this.$refs.abc.style.color = "red";
this.$refs.def.style.backgroundColor = "#D91A29";
this.$refs.def.style.color = "#fff";
this.$refs.ghi.style.backgroundColor = "#D91A29";
this.$refs.ghi.style.color = "#fff";
this.$refs.jkl.style.backgroundColor = "#D91A29";
this.$refs.jkl.style.color = "#fff";
this.allText = "显示全部";
this.zhongText = "显示中级物资";
this.gaoText = "显示高级物资";
this.zongwuzi = false;
this.zhongjiwuzi = false;
this.gaojiwuzi = false;
}
if(this.wangge == false){
this.$refs.abc.style.backgroundColor = "#D91A29";
this.$refs.abc.style.color = "#fff";
this.wangText = "显示网格";
}
}
实例生命周期钩子
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
beforeCreate
在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。
created
实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
beforeMount
在挂载开始之前被调用:相关的 render 函数首次被调用。
mounted
el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。
beforeUpdate
数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
updated
由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。
该钩子在服务器端渲染期间不被调用。
beforeDestroy
实例销毁之前调用。在这一步,实例仍然完全可用。
destroyed
Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。
看一下Vue中所有的生命周期怎么用的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue入门之生命周期</title>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<p>{{ number }}</p>
<input type="text" name="btnSetNumber" v-model="number">
</div>
<script>
var app = new Vue({
el: '#app',
data: {
number: 1
},
beforeCreate: function () {
console.log('beforeCreate 钩子执行...');
console.log(this.number)
},
cteated: function () {
console.log('cteated 钩子执行...');
console.log(this.number)
},
beforeMount: function () {
console.log('beforeMount 钩子执行...');
console.log(this.number)
},
mounted: function () {
console.log('mounted 钩子执行...');
console.log(this.number);
console.log("后边的钩子函数都不会执行");
},
beforeUpdate: function () {
console.log('beforeUpdate 钩子执行...');
console.log(this.number)
},
updated: function () {
console.log('updated 钩子执行...');
console.log(this.number)
},
beforeDestroy: function () {
console.log('beforeDestroy 钩子执行...');
console.log(this.number)
},
destroyed: function () {
console.log('destroyed 钩子执行...');
console.log(this.number)
},
});
</script>
</body>
</html>
再看一个综合的实战的例子,可能涉及到ajax和组件,不过先看一下vue的生命周期的例子的用法:
import Axios from 'axios' // 这是一个轻量级的ajax库,import是es6模块导入的语法。
export default { // 这是一个vue的模块,后面讲奥。
name: 'app',
components: {
},
data: function () {
return {
list: []
}
},
mounted: function () { // 挂在完成后的生命周期钩子注册。
this.$nextTick(function () { // 等待下一次更新完成后执行业务处理代码。
Axios.get('/api/menulist', {// 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新
params: {
}
}).then(function (res) {
this.list = res.data
}.bind(this))
})
}
}
全局 API
Vue.nextTick()
下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
官方文档的这句话的侧重点在最后那半句获取更新后的DOM,获取更新后的DOM言外之意就是什么操作需要用到了更新后的DOM而不能使用之前的DOM或者使用更新前的DOM或出问题,所以就衍生出了这个获取更新后的DOM的Vue方法。所以放在Vue.nextTick()回调函数中的执行的应该是会对DOM进行操作的 js代码
什么时候需要用的Vue.nextTick()
1.你在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中。原因是什么呢,原因是在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。
2.在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。
原因是,Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOm操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。
当你设置 vm.someData = 'new value',DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
vue.directive( id, [definition] )
注册或获取全局指令。
例子:
Vue.directive('my-directive', {
bind: function(){
//做绑定的准备工作
//比如添加事件监听器,或是其他只需要执行一次的复杂操作
},
inserted: function(){
//...
},
update: function(){
//根据获得的新值执行对应的更新
//对于初始值也会调用一次
},
componentUpdated: function(){
//...
},
unbind: function(){
//做清理操作
//比如移除bind时绑定的事件监听器
}
当指令的定义对象中只使用update时,只需直接传入函数即可,如下:
Vue.directive('my-directive', function(){
//...
})
应用
我们往往自定义指令都是定义到全局,方式如下:
第一步:建立一个全局的命令文件例如:directive/directives.js.
第二步:利用Vue.directive()建立一个全局命令,并将它暴露出来,例如一个focus 让表单自动聚焦.
directives.js
import Vue from 'vue';
Vue.directive('focus',{
//当绑定元素插入到DOM中
inserted: function(el){
el.focus(); //元素聚焦
el.setAttribute('placeholder','自定义内容');
}
})
Vue.directive('***',{
inserted: function(el){
//....
}
})
export {Vue}
第三步:在main.js(入口JS文件)中将它引入,可以省略文件后缀.
import directive from './components/global/directives';
这样任何一个Vue文件只要这样v-focus(命令名),就可以很方便的用到了
<el-input v-model="input" placeholder="" v-focus></el-input>
如果指令需要传值或者多个值
<body id="example">
<div id="demo" v-demo="{color : 'white',text : 'hello!'}"></div>
</body>
<script>
Vue.directive('demo',function(value){
console.info(value.color); //white
console.info(value.text) // hello!
})
var demo = new Vue({
el : '#demo'
})
</script>
Vue.filter( id, [definition] )
2.0中已经废弃了过滤器,需要我们自定义
<div id="app">
{{message|uppercase}}
</div>
//过滤器
Vue.filter('uppercase', function(value) {
if (!value) { return ''}
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
var vm = new Vue({
el:'#app',
data: {
message: 'test'
}
})
Vue.component
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。
所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子
组件在注册之后,便可以作为自定义元素 <my-component></my-component> 在一个实例的模板中使用。注意确保在初始化根实例之前注册组件:
<div id="example">
<my-component></my-component>
</div>
// 注册
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
// 创建根实例
new Vue({
el: '#example'
})
基础
- 通过使用 v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。但请留心这会影响到该节点上的其它数据绑定:
<span v-once>这个将不会改变: {{ msg }}</span>
- 原始HTML
双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html 指令:
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p> // 会将对应的color属性等解析出来,不再是纯粹的字符串
这个 span 的内容将会被替换成为属性值 rawHtml,直接作为 HTML——会忽略解析属性值中的数据绑定。注意,你不能使用 v-html 来复合局部模板,因为 Vue 不是基于字符串的模板引擎。反之,对于用户界面 (UI),组件更适合作为可重用和可组合的基本单位。
- 使用JavaScript表达式
迄今为止,在我们的模板中,我们一直都只绑定简单的属性键值。但实际上,对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持。
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
这些表达式会在所属 Vue 实例的数据作用域下作为 JavaScript 被解析。有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效。
<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}
<!-- 流控制也不会生效,请使用三元表达式 -->
{{ if (ok) { return message } }}
- 指令
指令 (Directives) 是带有 v- 前缀的特殊属性。指令属性的值预期是单个 JavaScript 表达式 (v-for 是例外情况,稍后我们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。回顾我们在介绍中看到的例子:
<p v-if="seen">现在你看到我了</p>
这里,v-if 指令将根据表达式 seen 的值的真假来插入/移除 <p> 元素。
参数
一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind 指令可以用于响应式地更新 HTML 属性:
<a v-bind:href="url">...</a>
在这里 href 是参数,告知 v-bind 指令将该元素的 href 属性与表达式 url 的值绑定。
另一个例子是 v-on 指令,它用于监听 DOM 事件:
<a v-on:click="doSomething">...</a>
修饰符
修饰符 (Modifiers) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault():
<form v-on:submit.prevent="onSubmit">...</form>
- 缩写
v-bind:
<!-- 完整语法 -->
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a>
v-on:
<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>
<!-- 缩写 -->
<a @click="doSomething">...</a>
它们看起来可能与普通的 HTML 略有不同,但 : 与 @ 对于特性名来说都是合法字符,在所有支持 Vue.js 的浏览器都能被正确地解析。而且,它们不会出现在最终渲染的标记中。缩写语法是完全可选的,但随着你更深入地了解它们的作用,你会庆幸拥有它们。
-
计算属性
的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。
计算属性例子:
div id="app">
<p>{{ message }}</p>
<p>{{ reversedMessage }}</p> // olleH
<p>{{ reversedMessage2}}</p> // 579
</div>
<script>
new Vue({
el: '#app',
data: {
message: 'Hello'
},
computed: {
// 计算属性的 getter
reversedMessage: function () {
// `this` 指向 vm 实例
return this.message.split('').reverse().join('')
},
reversedMessage2: function () {
// `this` 指向 vm 实例
return 123 + 456
}
}
});
</script>
你可能已经注意到我们可以通过在表达式中调用方法来达到同样的效果:
<p>Reversed message: "{{ reversedMessage() }}"</p>
// 在组件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
结论:
我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。
相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。
我们为什么需要缓存?假设我们有一个性能开销比较大的的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A 。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。
Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。细想一下这个例子:
<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
}
}
})
watch下边的属性值就是要检测的变量
上面代码是命令式且重复的。将它与计算属性的版本进行比较:
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
好得多了,不是吗?
计算属性默认只有 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]
}
}
}
对象语法
数组语法
用在组件上
对象语法
数组语法
自动添加前缀
多重值
在 Vue 中,我们使用 v-if 指令实现同样的功能
<h1 v-if="ok">Yes</h1>
也可以用 v-else 添加一个“else 块”:
<h1 v-if="ok">Yes</h1>
<h1 v-else>No</h1>
-
在
<template>
元素上使用v-if
条件渲染分组
因为 v-if 是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个 <template> 元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含 <template> 元素。
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
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 的元素的后面,否则它将不会被识别。
-
用
key
管理可复用的元素
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使 Vue 变得非常快之外,还有其它一些好处。例如,如果你允许用户在不同的登录方式之间切换:
<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>
这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 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>
现在,每次切换时,输入框都将被重新渲染。请看:
意,<label> 元素仍然会被高效地复用,因为它们没有添加 key 属性。
另一个用于根据条件展示元素的选项是 v-show 指令。用法大致一样:
<h1 v-show="ok">Hello!</h1>
不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS 属性 display。
v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
<div v-for="(value, key, index) in object">
{{ index }}. {{ key }}: {{ value }}
</div>
父组件传值到子组件:
组件实例的作用域是孤立的。这意味着不能 (也不应该) 在子组件的模板内直接引用父组件的数据。父组件的数据需要通过 prop 才能下发到子组件中。
子组件要显式地用 props
选项声明它预期的数据:
子组件的写法:(props是用来接收数据的)
Vue.component('child', {
// 声明 props
props: ['message'],
// 就像 data 一样,prop 也可以在模板中使用
// 同样也可以在 vm 实例中通过 this.message 来使用
template: '<span>{{ message }}</span>'
})
在父组件里边引用子组件的写法
<child message="hello!"></child>
结果:
hello!
-
自定义事件
子组件传值到父组件
(https://cn.vuejs.org/v2/guide/components.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6 "自定义事件")
我们知道,父组件使用 prop 传递数据给子组件。但子组件怎么跟父组件通信呢?这个时候 Vue 的自定义事件系统就派得上用场了。
另外,父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件(子组件传值到父组件)
在子组件里边
export default{
methods: {
onfilter(){
this.$emit("listenTochildEvent","browse");
},
}
}
在父组件:
<privateScoreTop :msg ="Widget" v-on:listenTochildEvent="showMessageFromChild"></privateScoreTop>
showMessageFromChild(data){
alert(data);
}
注意:不能用 $on 监听子组件释放的事件,而必须在模板里直接用 v-on 绑定
有时候,你可能想在某个组件的根元素上监听一个原生事件。可以使用 v-on 的修饰符 .native。例如:
<my-component v-on:click.native="doTheThing"></my-component>
有时候,非父子关系的两个组件之间也需要通信。在简单的场景下,可以使用一个空的 Vue 实例作为事件总线:
// 根组件(this.$root)
new Vue({
el: '#app',
router,
render: h => h(App),
data: {
// 空的实例放到根组件下,所有的子组件都能调用
Bus: new Vue()
}
})
// 触发组件 A 中的事件
<button @click="submit">提交<button>
methods: {
submit() {
// 事件名字自定义,用不同的名字区别事件
this.$root.Bus.$emit('eventName', 123)
}
}
// 在组件 B 创建的钩子中监听事件
// 当前实例创建完成就监听这个事件
created(){
this.$root.Bus.$on('eventName', value => {
this.print(value)
})
},
methods: {
print(value) {
console.log(value)
}
},
// 在组件销毁时别忘了解除事件绑定
beforeDestroy() {
this.$root.Bus.$off('eventName')
},
例子:
在一个弹窗里边修改了对应的游戏详情,修改成功之后,直接发送事件给游戏列表,让游列表进行刷新,达到适时修改的目的。
<div id="app">
<h1>我是父组件的标题</h1>
<childcomponent>
<!--如果组件里边没有值,slot就会插入进来,如果有,就不会插入,类似placeholder的占位符-->
<!--<p>我不是slot,而是插值</p>-->
</childcomponent>
</div>
<script>
Vue.component('childcomponent',{
template:'<div><h2>我是子组件的标题</h2><slot><div>我是子组件中slot值</div></slot></div>'
});
new Vue({
el:'#app'
})
</script>
结论:当没有插值时就解析slot中的代码,有插值时就不解析slot中的代码。最初在 <slot> 标签中的任何内容都被视为备用内容。备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容。
1.基本用法:vue2.0提供了一个keep-alive组件
用来缓存组件,避免多次加载相应的组件,减少性能消耗
<keep-alive>
<component>
<!-- 组件将被缓存 -->
</component>
</keep-alive>
有时候 可能需要缓存整个站点的所有页面,而页面一般一进去都要触发请求的
在使用keep-alive的情况下
<keep-alive><router-view></router-view></keep-alive>
将首次触发请求写在created钩子函数中,就能实现缓存,
比如列表页,去了详情页 回来,还是在原来的页面
2.缓存部分页面或者组件
(1)使用router. meta属性
// 这是目前用的比较多的方式
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
router设置:
routes: [
{ path: '/', redirect: '/index', component: Index, meta: { keepAlive: true }},
{
path: '/common',
component: TestParent,
children: [
{ path: '/test2', component: Test2, meta: { keepAlive: true } }
]
}
// 表示index和test2都使用keep-alive
(2).使用新增属性inlcude/exclude
2.1.0后提供了include/exclude两个属性 可以针对性缓存相应的组件
<!-- comma-delimited string -->
<keep-alive include="a,b">
<component :is="view"></component>
</keep-alive>
<!-- regex (use v-bind) -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>
//其中a,b是组件的name
注意:这种方法都是预先知道组件的名称的
(2)动态判断
<keep-alive :include="includedComponents">
<router-view></router-view>
</keep-alive>
includedComponents动态设置即可
-
自定义指令
除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。举个聚焦输入框的例子,如下:
当页面加载时,该元素将获得焦点 (注意:autofocus 在移动版 Safari 上不工作)。事实上,只要你在打开这个页面后还没点击过任何内容,这个输入框就应当还是处于聚焦状态。现在让我们用指令来实现这个功能:
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
如果想注册局部指令,组件中也接受一个 directives 的选项:
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
然后你可以在模板中任何元素上使用新的 v-focus 属性,如下:
<input v-focus>
在Vue 中可以把一系列复杂的操作包装为一个指令。
什么是复杂的操作?
我的理解是:复杂逻辑功能的包装、违背数据驱动的 DOM 操作以及对一些 Hack 手段的掩盖等。我们总是期望以操作数据的形式来实现功能逻辑。
钩子函数
对于自定义指令的定义,Vue2 有 5 个可选的钩子函数。
bind: 只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
inserted: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。
update: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。
componentUpdated: 被绑定元素所在模板完成一次更新周期时调用。
unbind: 只调用一次,指令与元素解绑时调用。
template
<div id="app">
<my-comp v-if="msg" :msg="msg"></my-comp>
<button @click="update">更新</button>
<button @click="uninstall">卸载</button>
<button @click="install">安装</button>
</div>
script(通过输出的结果判断触发的时机)
Vue.directive('hello', {
bind: function (el) {
console.log('bind')
},
inserted: function (el) {
console.log('inserted')
},
update: function (el) {
console.log('update')
},
componentUpdated: function (el) {
console.log('componentUpdated')
},
unbind: function (el) {
console.log('unbind')
}
})
var myComp = {
template: '<h1 v-hello>{{msg}}</h1>',
// 传递数据
props: {
msg: String
}
}
new Vue({
el: '#app',
data: {
msg: 'Hello'
},
components: {
myComp: myComp
},
methods: {
update: function () {
this.msg = 'Hi'
},
uninstall: function () {
this.msg = ''
},
install: function () {
this.msg = 'Hello'
}
}
})
结论:
页面加载时:bind inserted
点击“更新”按钮,更改数据触发组件更新。
update
componentUpdated
卸载组件时
点击“卸载”按钮,数据置空否定判断以触发组件卸载。
unbind
卸载组件时
点击“卸载”按钮,数据置空否定判断以触发组件卸载。
unbind
重新安装组件时
点击“安装”按钮,数据赋值肯定判断以触发组件重新安装。
bind
inserted
区别:
从案例的运行中,对 5 个钩子函数的触发时机有了初步的认识。存疑的也就是bind和inserted、update和componentUpdated的区别了。
bind 和 inserted:
据文档所说,插入父节点时调用 inserted,来个测试。
bind: function (el) {
console.log(el.parentNode) // null
console.log('bind')
},
inserted: function (el) {
console.log(el.parentNode) // <div id="app">...</div>
console.log('inserted')
}
分别在两个钩子函数中输出父节点:bind 时父节点为 null,inserted 时父节点存在。
update 和 componentUpdated
关于这两个的介绍,从字眼上看感觉是组件更新周期有关,继续验证。
update: function (el) {
console.log(el.innerHTML) // Hello
console.log('update')
},
componentUpdated: function (el) {
console.log(el.innerHTML) // Hi
console.log('componentUpdated')
}
没毛病,update 和 componentUpdated 就是组件更新前和更新后的区别。
最佳实践
根据需求的不同,我们要选择恰当的时机去初始化指令、更新指令调用参数以及释放指令存在时的内存占用等。
比较常见的场景是:用指令包装一些无依赖的第三方库以扩展组件功能。而一个健壮的库通常会包含:初始化实例、参数更新和释放实例资源占用等操作。
Vue.directive('hello', {
bind: function (el, binding) {
// 在 bind 钩子中初始化库实例
// 如果需要使用父节点,也可以在 inserted 钩子中执行
el.__library__ = new Library(el, binding.value)
},
update: function (el, binding) {
// 模版更新意味着指令的参数可能被改变,这里可以对库实例的参数作更新
// 酌情使用 update 或 componentUpdated 钩子
el.__library__.setOptions(Object.assign(binding.oldValue, binding.value))
},
unbind: function (el) {
// 释放实例
el.__library__.destory()
}
})
这是一个使用了这些属性的自定义钩子样例:
<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!'
}
})
函数简写
在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如这样写:
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value
})
对象字面量
如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令函数能够接受所有合法的 JavaScript 表达式。
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
Vue.directive('demo', function (el, binding) {
console.log(binding.value.color) // => "white"
console.log(binding.value.text) // => "hello!"
})
插件
开发插件
插件通常会为 Vue 添加全局功能。插件的范围没有限制——一般有下面几种:
添加全局方法或者属性,如: vue-custom-element
添加全局资源:指令/过滤器/过渡等,如 vue-touch
通过全局 mixin 方法添加一些组件选项,如: vue-router
添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
一个库,提供自己的 API,同时提供上面提到的一个或多个功能,如 vue-router
Vue.js 的插件应当有一个公开方法 install
。这个方法的第一个参数是 Vue
构造器,第二个参数是一个可选的选项对象:
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或属性
Vue.myGlobalMethod = function () {
// 逻辑...
}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})
// 3. 注入组件
Vue.mixin({
created: function () {
// 逻辑...
}
...
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}
使用插件
通过全局方法 Vue.use() 使用插件:
// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)
也可以传入一个选项对象:
Vue.use(MyPlugin, { someOption: true })
Vue.use 会自动阻止多次注册相同插件,届时只会注册一次该插件。
Vue.js 官方提供的一些插件 (例如 vue-router) 在检测到 Vue 是可访问的全局变量时会自动调用 Vue.use()。然而在例如 CommonJS 的模块环境中,你应该始终显式地调用 Vue.use():
// 用 Browserify 或 webpack 提供的 CommonJS 模块环境时
var Vue = require('vue')
var VueRouter = require('vue-router')
// 不要忘了调用此方法
Vue.use(VueRouter)
awesome-vue 集合了来自社区贡献的数以千计的插件和库。