一 自定义v-model属性
vue在表单等domm模型中默认绑定了v-model的特性,而父组件中封装了有input的子组件,那么如何使父子组件都能进行双向绑定呢?这里提供一个案例:
父组件有一个custom-model子组件:
<template>
<div>
<p>{{name}}</p>
<custom-model v-model="name" />
</div>
</template>
<script>
import customModel from './CustomModel';
export default {
components: {
customModel
},
data() {
return {
name: ''
}
}
}
</script>
子组件custom-model:
<template>
<input type="text"
:value="text1"
@input="$emit('change1', $event.target.value)"
/>
<!-- 上面的input使用了:value而非v-model -->
<!-- 上面的change1和model.event对应起来 -->
<!-- text1属性对应起来 -->
</template>
<script>
export default {
model: {
prop: 'text1',
event: 'change1'
},
props: {
text1: String,
default() {
return ''
}
}
}
</script>
父组件中的子组件自定义v-model,该值会与子组件model中的prop进行双向数据绑定,同时提供event作为事件名进行事件注册。
二 $nextTick
由于vue是异步渲染的,所以在修改vue的data等数据之后,等所有函数执行完才会进行dom渲染,所以在函数执行中,要在dom渲染后执行外面要包裹一层$nextTick,下面有一个案例:
<template>
<div>
<ul ref="ul1">
<li v-for="(item,index) in list" :key="index">
{{item}}
</li>
</ul>
<button @click="add">add</button>
</div>
</template>
<script>
export default {
data() {
return {
list: ['a', 'b', 'c']
}
},
methods: {
add() {
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
// 异步渲染 $nextTick等待dom渲染完成后执行
// 页面渲染时将会对data的修改做整合,多次data修改只会修改一次
this.$nextTick(() => {
const ul1 = this.$refs.ul1
console.log(ul1.childNodes.length)
})
}
}
}
</script>
如果去掉this.$nextTick,在函数中打印的dom长度就不是6而是3,可以尝试一下。
三 slot插槽
高级的插槽使用作用域插槽和具名插槽,案例:
父组件
<template>
<slot-name>
<!-- 作用域插槽 -->
<template v-slot="slotProps">
{{slotProps.slotData.name}}
</template>
<!-- 具名插槽 -->
<template v-slot:footer>
<p>aaa</p>
</template>
</slot-name>
</template>
<script>
import slotName from './slotName';
export default {
components: {
slotName
}
}
</script>
子组件
<template>
<div>
<a :href="website.url">
<slot :slotData="website">
哈哈哈
</slot>
</a>
<slot name="footer"></slot>
</div>
</template>
<script>
export default {
data() {
return {
website: {
url: 'http://www.baidu.com',
name: '百度'
}
}
}
}
</script>
子组件在slot中传入slotData属性和对应的value,到父组件v-slot的名称.slotData属性就是子组件website的值,这里注意一下在具名插槽footer里的插槽不能使用slotData的值,所以交作用域插槽,只是在这个slot内可以使用的变量
四 动态组件,异步组件
动态组件:
<Component :is="'text'" />
在子组件不确定的情况下,通过字符串传参表示所对应的类的名称,组件能够直接渲染出来,照常引用、注册组件,使用component标签并添加:is属性,属性值为组件名
异步组件:
普通组件采用import xx from xx的方式加载,为同步加载,当项目需要引入很大的组件时,同步加载性能会十分差,体验也很不理想,所以此时需要采用异步加载组件的方法。
方法:直接在components里定义组件名,使用import()方法
export default{
components:{
${组件名}:()=>import(${组件路径})
}
}
能够在组件初始化时不用渲染而等到需要使用的时候再进行渲染,在路由的配置中经常使用。
五 keep-alive
使用场景:频繁切换,不需要重复渲染的组件
被包裹的组件会被缓存,在频繁切换但不需要重复渲染的情况下使用,通常是vue其中一个性能优化的方向
用法:需要频繁切换的组件外层添加keep-alive标签,添加后组件切换时不会被销毁,会缓存起来,大幅提高渲染性能。
案例:
这是父组件,会同时切换两个组件
<template>
<div>
<keep-alive>
<template v-if="data === 'c'">
<c />
</template>
<template v-if="data === 'd'">
<d />
</template>
</keep-alive>
<button @click="handle">提交</button>
</div>
</template>
<script>
import c from './c';
import d from './d';
export default {
components: {
c,
d
},
data() {
return {
data: 'c'
}
},
methods: {
handle() {
this.data === 'c' ? this.data = 'd' : this.data = 'c'
}
}
}
</script>
其中c组件和d组件基本相同只展示其中一个
<template>
<div>c</div>
</template>
<script>
export default {
mounted() {
console.log('c mounted')
},
destroyed() {
console.log('c destoryed')
}
}
</script>
如果没有keep-alive,那么切换的时候destoryed函数也会执行,但是有的情况destoryed函数就不执行了,原因就在于keep-alive会缓存组件,组件在其中不会销毁。
六 mixin
组件抽离公共逻辑
多个组件有相同的逻辑可以抽离出来的时候就可以使用。但是目前mixin并不是完美的解决方案,会有一些问题。现在的Vue3意在解决这些问题。
案例:
<template>
<div>
{{a}}--{{b}}
<button @click="handle">提交</button>
</div>
</template>
<script>
import myMixins from './mixins';
export default {
mixins: [ myMixins ],
data() {
return {
a: 'aaa'
}
}
}
</script>
export default {
data() {
return {
b: 'bbb'
}
},
methods: {
handle() {
console.log(this.b)
}
}
}
mixin目前存在的缺点:
- 变量来源不明确,不利于阅读。
- 多mixin可能会造成命名冲突。
- mixin可能存在多对多的关系,复杂度较高。
七 watch进阶
从我们刚开始学习Vue的时候,对于侦听属性,都是简单地如下面一般使用:
watch:{
a(){
//doSomething
}
}
实际上,Vue对watch提供了很多进阶用法。
handler函数
以对象和handler函数的方式来定义一个监听属性,handler就是处理监听变动时的函数:
watch:{
a:{
handler:'doSomething'
}
},
methods:{
doSomething(){
//当 a 发生变化的时候,做些处理
}
}
handler有啥用?是多此一举么?用途主要有两点:
- 将处理逻辑抽象出去了,以method的方式被复用
- 给定义下面两个重要属性留出了编写位置
deep属性
当watch的是一个Object类型的数据,如果这个对象内部的某个值发生了改变,并不会触发watch动作!
也就是说,watch默认情况下,不监测内部嵌套数据的变动。但是很多情况下,我们是需要监测的!
为解决这一问题,就要使用deep属性:
watch:{
obj:{
handler:'doSomething',
deep:true
}
},
methods:{
doSomething(){
//当 obj 发生变化的时候,做些处理
}
}
deep属性默认为false,也就是我们常用的watch模式。
immediate属性
watch 的handler函数通常情况下只有在监听的属性发生改变时才会触发。
但有些时候,我们希望在组件创建后,或者说watch被声明和绑定的时候,立刻执行一次handler函数,这就需要使用immediate属性了,它默认为false,改为true后,就会立刻执行handler。
watch:{
obj:{
handler:'doSomething',
deep:true,
immediate:true
}
},
methods:{
doSomething(){
//当 obj 发生变化的时候,做些处理
}
}
同时执行多个方法
使用数组可以设置多项,形式包括字符串、函数、对象
watch: {
// 你可以传入回调数组,它们会被逐一调用
a: [
'handle1',
function handle2 (val, oldVal) { /* ... */ },
{
handler: function handle3 (val, oldVal) { /* ... */ },
/* ... */
}
],
}