- Vue.js: Up and Running,中文版是Vue.js快跑:构建触手可及的高性能Web应用
- Vue.js 2 Web Development Projects,中文版是Vue.js项目实战
- 1 v-if与v-show
- 2 向Vue实例添加反应式属性
- 3 重新审视Vue实例的内容
- 4 Vue操作数组
- 5 双向绑定的补充
- 6 computed属性的set
- 7 watch监听对象属性和获取新旧值
- 7.1 监听对象的属性
- 7.2 监听时获取变化前后的值
- 7.3 深度监听
- 8 filter补遗
- 9 ref引用
- 10 处理事件
- 11 过渡与动画
v-if与v-show
v-show需要先把模板处理完毕,而v-if不需要。看下边这个例子:
<div id="app">
<div v-show="user">
<p>User name: {{ user.name }}</p>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
user: undefined
}
});
</script>
这里会报错,因为Vue会尝试去获取user.name。如果将v-show改成v-if,内部的元素根本不会去被解释和渲染,所以不会报错。
采用这个方法,可以在父子组件通信的时候先进行父组件传来的数据的判断。
v-show的好处是开销小。v-if变动的时候,其内部组件会被重新计算和渲染,而v-show不会,如果需要展示经常变化的数据,v-show更合适一些。。
向Vue实例添加反应式属性
在之前QQ群讨论里,有朋友就说面试时候被问到如何给Vue实例添加属性,不用说,肯定是问如何添加反应式的属性。 Vue是在初始化实例的时候对data内的对象添加监听方法,为了要给Vue添加反应式的新属性有如下几种方法: 主要有三种方法:- 给要添加的属性提前赋值为undefined,然后再赋值,当然这种其实不叫做添加了,因为已经让Vue初始化了
- 使用
Object.assign(),创建一个新对象覆盖原来data的对象。一会看下边例子。 - 使用
Vue.set(object,name, value),其中object是data中的对象,name是属性名,value设置的属性的值。在组件里想要使用这个方法,则可以通过this.$set来使用这个方法。
<div id="app" class="container">
<template v-if="formData.user">
<div>这是新添加的user属性: {{formData.user}}</div>
</template>
<div>这是原来的属性: {{formData.username}}</div>
</div>
<script>
var app= new Vue({
el: '#app',
data: {
formData: {
username: 'jenny',
}
},
});
</script>
启动这个实例后,在控制台里输入app.formData = Object.assign({},app.formData,{user:'cony'}),页面就能渲染出来新添加的属性。
再来看第三种方法的实例:
<div id="app" class="container">
<template v-if="formData.user">
<div>这是新添加的user属性: {{formData.user}}</div>
</template>
<div>这是原来的属性: {{formData.username}}</div>
<button @click="addProperty">添加user=cony属性</button>
</div>
<script>
var app= new Vue({
el: '#app',
data: {
formData: {
username: 'jenny',
}
},
methods:{
addProperty:function () {
this.$set(this.formData,'user','cony')
}
}
});
</script>
点击这个按键,就会看到新添加的属性被显示了出来。
重新审视Vue实例的内容
在之前学的时候,总以为数据放data,需要计算的放在computed里边,事件处理放在methods中。这也是被Vue.js实战这本书按照实例直接形成的想法。 实际上Vue实例就将其看成一个对象,其中的内容可以将其分为properties和methods两大类。
data就是纯粹的保存数据,而methods实际上就是这个Vue实例的方法。而computed介于属性和方法之间,采用函数进行计算,采用属性进行访问。
另外就是this的使用:
- 在模板渲染的双花括号里,不要使用
this,Vue直接会去找data,computed和methods对应的名称 - 在
methods里访问属性和方法,需要加上this,这个this表示方法所属的Vue实例对象,可以用其调用其他属性和方法(以及computed属性)。 - 在
computed里访问属性和方法,也需要加上this - 在
watch里访问属性和方法,也需要加上this - 在
filter中无法通过this访问Vue实例的内容
Vue操作数组
Vue里不能够直接通过索引操作数组。看下边这个例子:<div id="app" class="container" v-cloak="">
<p>{{games}}</p>
</div>
<script>
var app= new Vue({
el: '#app',
data: {
games: ['Baldur\'s gate 3','Baldur\'s gate 2','Neverwinter Nights','TotalWar: ThreeKingdoms', 'Planescape: Torment']
},
methods:{
}
});
</script>
如果在控制台中输入app.games[2]='Neverwinter Nights 2',会发现数组没有任何变化,这是因为Vue在代理数组的时候,没有针对索引进行监听,而是针对数组的原生方法进行监听。
想要更改数组的内容,有如下几种办法:
- Vue内置了对于
.splice等数组常用内部方法的监听,所以可以用这些方法来更改。 - 用上边提到的
Vue.set直接更新整个数组
双向绑定的补充
双向绑定已经了解了其实是一个语法糖,会绑定value属性并且通过事件进行监听。
双向绑定之后,HTML元素的value,checked和selected属性会失效,这个要注意。
由于绑定可以用来监听value属性,所以对于radio,checkbox,select都可以用来监听。只需要给要监听的一组内容绑定同一个属性即可。而且还可以反向传递value来控制选中哪个内容。
computed属性的set
computed设置的东西,也像一个属性一样,可以实例内访问。data,computed和methods的区别如下:
| 名称 | 可读 | 可写 | 是否能接受参数 | 动态计算 | 缓存 |
|---|---|---|---|---|---|
| data | Yes | Yes | No | No | 不会被计算 |
| methods | Yes | No | Yes | Yes | No,每次调用都重新计算 |
| computed | Yes | Yes | No | Yes | Yes |
computed属性也可以set,要通过设置具体的get和set方法才行:
<div id="app">
<p>{{numbers}}</p>
<p>Sum of numbers: {{ numberTotal }}</p>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
numbers: [5, 8, 3]
},
computed: {
numberTotal: {
get() {
return this.numbers.reduce((sum, val) => sum + val);
},
set(newValue) {
const oldValue = this.numberTotal;
const difference = newValue - oldValue;
this.numbers.push(difference)
}
}
}
});
</script>
这里的get就是访问属性的时候得到的结果。set方法则是传入一个新要设置的值作为参数,然后在set方法中进行更改。
这样就极大的提高了computed属性的灵活性。
watch监听对象属性和获取新旧值
watch的内容比较多,要分成几个部分总结。
监听对象的属性
watch能做的事情,如果只是纯粹更改数值,最好还是使用methods和computed,否则逻辑会乱。watchers的真正意义在于异步进行一些操作,比如模拟一个异步:
<div id="app">
<input type="text" v-model="inputValue">
<p>Five seconds ago, the input said "{{ oldInputValue }}".</p>
</div>
<script>
new Vue({
el: '#app',
data: {
inputValue: '',
oldInputValue: ''
},
watch: {
inputValue() {
const newValue = this.inputValue;
setTimeout(() => {
this.oldInputValue = newValue;
}, 5000);
}
}
});
</script>
注意watch是在数字变动之后才执行的。这里就监听了count属性,然后每次有变动的时候,获取新的属性,过五秒在设置到oldInputValue上。
普通情况下,如果watch一个对象,只有整个对象被替换成其他对象,才会触发watch。修改对象的属性,不会触发watch,这个时候需要特殊的写法:
<div id="app">
<p>outer对象的value是{{outer.value}}</p>
</div>
<script src="https://unpkg.com/vue"></script>
<script>
var app = new Vue({
el: '#app',
data: {
outer: {
value: 1,
}
},
watch: {
'outer.value'() {
console.log('outer.value watcher 被触发了')
},
outer() {
console.log('outer watcher整个对象被替换了')
}
}
});
</script>
如果在控制台输入app.outer.value =3,会发现只触发了'outer.value'的监听,如果输入app.outer={},就可以看到两个监听器都被触发了。
监听时获取变化前后的值
watch还一个重要的内容是可以获取变化前后的值,这两个值是作为watch方法的两个参数存在:
<div id="app">
<p>outer对象的value是{{outer.value}}</p>
</div>
<script src="https://unpkg.com/vue"></script>
<script>
var app = new Vue({
el: '#app',
data: {
outer: {
value: 1,
}
},
watch: {
'outer.value'(newval, oldval) {
console.log('outer.value watcher 被触发了');
console.log('新的值是:' + newval);
console.log('老的值是:' + oldval);
},
outer() {
console.log('outer watcher整个对象被替换了')
}
}
});
</script>
可以方便的通过参数来获取要设置的值和原来的值,这就可以进行一些特殊的操作。
深度监听
如果要监听一个对象所有的属性变化,就需要将watch拆开来写:<div id="app">
<p>outer对象的value是{{outer.value}}</p>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
outer: {
value: 1,
inner:{
value:2
}
}
},
watch: {
outer: {
handler(newval, oldval) {
console.log(newval, oldval);
},
deep: true
}
}
});
</script>
这里实际上是递归增加了所有监听器,所以不能直接显示前后的值,监听某个具体属性是可以的。watch的其他一些高级用法可以看这里。
过滤器filter补遗
filter本质上就是接受一个参数的函数。一般来说可以有两种方法编写过滤器:- 在Vue实例的
filters属性之内编写 Vue.filter(filtername,function)来注册
ref引用
已经知道了<canvas ref="myCanvas"></canvas>这样一个元素可以用this.$refs.myCanvas来获取对应的vnode节点对象。
这里还需要知道的是this.$refs只是对当前组件内的元素进行查找,不会查到其他组件去,所以可以很方便的使用,省去了使用queryselector等方式定位元素。
处理事件
之前的内容主要都是数据展示,还不涉及到交互。如果要使用交互,就要涉及到处理事件了。 处理事件的时候,可以直接写在绑定内部,是因为已经知道绑定内部的东西都按照JS语句解析。 但实际上,还是采用处理函数比较方便,因为处理函数的第一个参数会被传入事件对象。修饰符
prevent,阻止默认事件,常用于a标签,submit类型的按钮之类stop,阻止冒泡,很常用once,只有第一次才生效capture,这是一个新了解的,即元素自身触发的事件先在此处理,然后才交由内部元素进行处理。像反向冒泡。self,event.target是当前元素,即事件不是从内部冒泡上来的。
详细的生命周期
一个Vue实例的生命周期对应八个钩子,其实是四个大阶段+四个before,具体如下:new Vue()执行,新实例初始化beforeCreate调用,反应式功能初始化created调用,模板被初始化,会根据组件的template和render函数等初始化模板,模板还未加载到DOM上beforeMount调用,根据模板生成了HTML元素,依然没有挂载到DOM上mounted在生成好HTML元素之后调用,Vue 2.0中不能保证这个钩子调用之后Vue实例立刻被挂载到DOM上,必须在mounted中调用Vue.nextTick()(组件中使用this.$nextTick())才能确保,这个还有一个回调函数可以执行自己的代码。详细看下边例子。此钩子执行完毕之后,用户可以看到DOM元素,但数据还没更新上去。beforeUpdate在数据还没有更新到DOM上之前调用,去计算新数据和准备更新updated在用数据更新DOM完成之后调用,这个钩子会反复调用,如果有多个地方需要更新数据beforeDestroy调用,在需要从DOM中去除组件的时候调用destroyed调用,从DOM中去除组件之后调用
Vue.nextTick()的例子是:
<script>
new Vue({
el: '#app',
mounted() {
// Element might not have been added to the DOM yet
this.$nextTick(() => {
// Element has definitely been added to the DOM now
});
}
});
</script>
过渡与动画
Vue提供了很多过渡效果,之前一直没有看过,现在要稍微入入门看看。CSS过渡效果
Vue提供了一个组件<transition>,这个组件会给其中的所有元素添加一个v-if指令和一个css类,这样就可以通过CSS类给元素添加挂载到DOM和从DOM移除时候的动画效果。
下边是一个初始的例子:
<div id="app">
<button @click="divVisible = !divVisible">Toggle visibility</button>
<div v-if="divVisible">This content is sometimes hidden</div>
</div>
<script src="https://unpkg.com/vue"></script>
<script>
new Vue({
el: '#app',
data: {
divVisible: true
}
});
</script>
这个例子很简单,就是点击按钮来切换下边的元素是否显示,由于我们使用了v-if,所以这个元素每次被移入和移出DOM树。
先来通过<transition>添加一个简单的效果:
<transition name="fade">
<div v-if="divVisible">This content is sometimes hidden</div>
</transition>
单独加上这个也没效果,必须配合样式:
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
这个生效的原理是:Vue会使用<transition name="fade">中的name属性的值,加上一些后缀拼合成在不同阶段应用的CSS类名,这些类及对应作用是:
{name}-enter,元素添加进DOM的时候会被加上这个类,在1帧之后就会取消这个类。用来设置元素进入DOM之后初始并且想要逐渐消失的样式,例子里设置了完全透明,在元素进入的时候,一开始就是完全透明的。{name}-enter-active,这个样式在整个加入过程中都生效,和{name}-enter同时添加到元素上,在动画完成后移除。这个类里最好设置CSS的transition属性,包括动画时间,属性和过渡曲线。{name}-enter-to在{name}-enter被移除的时候添加到元素上,一般用于设置逐渐出现在元素上的样式。{name}-leave,在触发要移除DOM的第一帧添加上,然后下一帧就会移除{name}-leave-active,在移除的全过程生效。{name}-leave-to,很像enter-to,当{name}-leave被移除的时候添加到元素上。
{name}-enter、{name}-enter-active、{name}-leave-active和{name}-leave-to四个类。
JS过渡效果
JS过渡就是<transition>在不同阶段提供的钩子,就可以使用JS库来写动画。
这里的钩子,是指特殊的事件,在<transition>元素上监听这些事件然后写处理函数就可以了。
钩子和CSS动画的阶段本质上是一致的,列表如下:
before-enter,在进入动画开始之前执行,用于设置初始样式enter,进入动画开始的时候执行,可以在其中使用done回调来指示动画结束afterEnter,进入动画结束的时候调用enterCancelled,进入动画被取消的时候调用beforeLeave,移出动画开始之前调用,用于设置移出的初始样式leave,移出动画开始的时候执行afterLeave,和上边的阶段类似leaveCancelled和上边的阶段类似
<div id="app">
<button @click="divVisible = !divVisible">Toggle visibility</button>
<transition
v-on:before-enter="handleBeforeEnter"
v-on:enter="handleEnter"
v-on:leave="handleLeave">
<div v-if="divVisible">The content</div>
</transition>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js"></script>
<script src="https://unpkg.com/vue"></script>
<script>
new Vue({
el: '#app',
data: {
divVisible: false
},
methods: {
handleBeforeEnter(el) {
el.style.opacity = 0;
},
handleEnter(el, done) {
TweenLite.to(el, 1, { opacity: 1, onComplete: done });
},
handleLeave(el, done) {
TweenLite.to(el, 1, { opacity: 0, onComplete: done });
}
}
});
</script>
果然还是老外写的书好,昨天看了一会,就发现还有如此多的细节没有掌握。
现在对于一个Vue实例的作用也比较清楚了。data用来定义原始的数据,computed用于基于原始数据生成可以反复使用的结果,methods就是这个实例的方法,可以包括各种方法,无论是普通功能还是事件调用。当然不同的方法有不同的参数,尤其是处理事件的方法。watch则用于对数据的异步处理。
前边几个属性都和Vue实例有着紧密的联系,而filter是比较独立的功能函数,无法通过this访问实例。
这样基本上一个实例的内部就理清楚了。把实例进化到组件的话,还要再添加一个props作为父组件传入的基础数据,和data一样共同构成组件的基础数据。