请注意插槽不是响应性的。如果你需要一个组件可以在被传入的数据发生变化时重渲染,我们建议改变策略,依赖诸如
props
或data
等响应性实例选项。-- vm.$slots
问题描述
项目中自定了组件 widget,作为容器,其中 header 部分做了预置插槽 slot,用于信息的展示;在实际使用过程中,header 内容需要根据不同条件展示不同信息。至此,问题出现了~~~
期望效果,右侧 title 根据 ajax 返回结果,展示其中一种
实际效果,ajax 正常返回,但没有渲染成功
widget 代码
<template>
<div class="widget-container">
<header v-if="titleSlots.length">
<div v-for="(item, index) in titleSlots" :key="index">
<slot :name="item"></slot>
</div>
</header>
<main v-if="$slots.default">
<slot></slot>
</main>
<footer v-if="$slots.footer">
<slot name="footer"></slot>
</footer>
</div>
</template>
<script>
const TITLE_SLOTS_TYPES = ['title-left', 'title-center', 'title-right'] // title-slots类型
export default {
name: 'widget',
computed: {
titleSlots () {
return TITLE_SLOTS_TYPES.filter(name => this.$slots[name])
}
}
}
</script>
① slots 是动态渲染的;② titleSlots 值是由 vm.$slots
计算(computed)而来。
业务中使用代码
<template>
<widget>
<template slot="title-left">
<span>任务名称:{{taskName}}</span>
</template>
<template slot="title-right">
<el-button v-if="condition1">开始报送</el-button>
<el-button v-else-if="condition2">重新报送</el-button>
<el-button v-else-if="condition3">报送中</el-button>
</template>
</widget>
</template>
① condition
条件是通过异步 ajax 请求返回,初始状态slot为空;② 请求成功后,其中某一条件生效,但不展示!
问题拆解
将上述示例进行简化,以便剖解问题。
Test.vue
<template>
<div>
<slot name="content" v-if="isShow"></slot>
</div>
</template>
<script>
export default {
name: 'test',
computed: {
isShow () {
return !!this.$slots.content
}
},
created () {
console.log(this.$slots.content)
}
}
</script>
App.vue
<template>
<test ref="test">
<template slot="content">
<p v-if="count === 1">1</p>
<p v-else-if="count === 2">2</p>
<p v-else-if="count >= 3">3</p>
</template>
</test>
<button @click="getTestSlots">获取test组件slots</button>
</template>
<script>
import Test from './components/Test.vue'
export default {
name: 'App',
components: { Test },
data () {
return {
count: 0
}
},
methods: {
getTestSlots () {
console.log(this.$refs['test'].$slots)
}
},
created () {
// 模拟 ajax
setTimeout(() => {
this.count = 3
}, 3000)
}
}
</script>
【问题1】slot 内容为空,组件内部 vm.$slots
无法获取相应内容
如示例,在初始状态,任何一个 condition 都不成立,此时组件内部 vm.$slots
是获取不到相应 slot 的。
console.log(this.$refs['test'].$slots) // getTestSlots App.vue
结果:{}
console.log(this.$slots.content) // created Test.vue
结果:undefined
【问题2】$slots
不具备响应性
如示例, 等待 3000 ms,此时 count 值变成了3,但 test 组件依然未渲染
console.log(this.$refs['test'].$slots) // getTestSlots App.vue
结果:{content: [VNode]}
isShow(Computed)未生效,但 vm. s l o t s 中是有值的,应征了官方 a p i 中提到的:“ ‘ v m . slots 中是有值的,应征了官方 api 中提到的:“`vm. slots中是有值的,应征了官方api中提到的:“‘vm.slots`不具备响应性!!”
问题解决
问题的核心:组件内依赖
$slots
来判断是否渲染相应的 slot 内容;而业务端调用时,初始时不存在,数据变化时,$slots
不具备响应性(computed也就不会生效),从而相应的 slot 也就无法显示。
【方案1】规避默认不存在 slot 的情况
初始时,让相应 slot 具备内容,组件中也就无需依赖 computed 。
App.vue
<test ref="test">
<template slot="content">
<div>
<p v-if="count === 1">1</p>
<p v-else-if="count === 2">2</p>
<p v-else-if="count >= 3">3</p>
</div>
</template>
</test>
增加一层元素<div>
,确保在自定义组件初始化过程中,即可获取 $slots
。
【方案2】规避到 “vm.$slots
不具备响应性 “
改变策略,依赖 props 等响应性实例
App.vue
<test ref="test" :isShow="count > 0">
...
</test>
Test.vue
props: ['isShow']
【其他】相同父元素的子元素渲染错误
不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。
<p v-if="count === 1" key="1">1</p>
<p v-else-if="count === 2" key="2">2</p>
<p v-else-if="count >= 3" key="3">3</p>