zl程序教程

您现在的位置是:首页 >  Javascript

当前栏目

vue2.0 插槽不是响应性的

2023-02-25 18:17:24 时间

请注意插槽不是响应性的。如果你需要一个组件可以在被传入的数据发生变化时重渲染,我们建议改变策略,依赖诸如 propsdata 等响应性实例选项。-- 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>