如何解决--在渲染函数之外调用插槽的问题

2620次阅读  |  发布于2年以前

如果你是用 Vue 来开发项目的,那么,你曾经有可能访问 slot.default() 遇到如下问题:

Slot "default" invoked outside of the render function:
this will not track dependencies used in the slot. 
Invoke the slot function inside the render function instead. 

本文本中,将会解释这个错误背后的原因以及如何解决这个问题。

插槽的调用需要发生在渲染函数或模板中。要抑制这个错误,我们只需要把代码移到一个计算的属性或从模板或渲染函数中调用的方法中。

“this will not track dependencies used in the slot” 指的是什么?

错误信息解释了问题产生的本质原因,但这个提示不是很清晰,无法帮助我们界定问题的本质。下面,我们来详细介绍下错误背后的原因产生。

this will not track dependencies used in the slot.

经过一些调查,我做了一个可复现的代码,并理解了在渲染函数之外使用slots.default()语法的含义。为了理解这个问题,我们先复习一下 Vue 的响应式原理。

Vue 的响应式性系统允许我们声明属性、数据和计算属性,而不需要跟踪它们的变化。响应式性系统在幕后工作,确保我们的变量始终是最新的。

在Vue框架内,最常见的响应式特征的情况是使用 computed

计算属性指的是一个变量,它可以被用来以有效和响应式的方式修改和操作你的组件中的数据和属性。

计算属性的一个简单例子是博客片段,我们把一篇完整的博客文章作为属性传递,并把它截断成一定数量的字符。另一个更常见的例子是一个简单的变量,用来定义一个按钮的文本,根据当前的状态 "显示 "或 "隐藏"。

举例来说,在 "expanded"的值被改变之前,下面的属性将永远不会再被运行。

const buttonText = computed( () => {
  return expanded.value ? 'Show less' : 'Show more';
});

除非 expanded 的值发生变化,否则上述方法不会再被触发。Vue 在幕后所做的观察 expanded 变量的工作就是所谓的 "跟踪依赖性"。

你可能已经意识到了,"跟踪依赖" 这几个字和Vue框架在试图访问插槽时产生的错误中提到的一样。事实上,这个错误是为了告诉我们,在渲染函数之外使用slots.default()的语法,会使变量失去响应性,因此它不会 "跟踪" 任何可能影响它的变化。

拿上面的例子来说,失去依赖关系的跟踪将意味着无论 expanded 的值是多少,按钮都不会改变。

// 下面的代码只是为了说明问题
//  我们只是假设了一个具有跟踪依赖性的变量,这也是我们插槽发生的情况
const expanded = ref( false ); //Broken Tracking

console.log(buttonText)
// 输出 "Show more"

expanded.value = true;

console.log(buttonText)
// 输出: "Show more"  值没有没有改变,因为Vue无法跟踪 expanded 的变化。

在我们的代码库中,未被追踪的变量不是我们想要的东西,应该要尽量的避免它。

如何确保 Vue 插槽被跟踪依赖

接下来,我们分析下可以做些什么来确保我们的插槽有一个响应式的跟踪系统,确保不会更新失败

通过确保我们的槽调用发生在渲染函数和模板中,问题就可以解决了,正如错误信息中提到的那样。

Invoke the slot function inside the render function

我们现在要介绍两种不同的情况。第一种是在使用渲染函数时调用插槽函数,第二种是在使用vue单文件组件的<template>部分。

在渲染函数中使用插槽

当在一个有渲染函数的组件中使用插槽时,我们必须确保在渲染函数的 "return"语句中调用插槽函数,而不是在 setup 中。

// 不好
import { h } from 'vue'

export default {
  setup( props, { slots } ) {
    const defaultSlot = slots.default();
    return () => h('div', defaultSlot)
  }
}

// 好
import { h } from 'vue'

export default {
  setup( props, { slots } ) {
    return () => h('div', slots.default())
  }
}

在使用单一文件组件(SFC)时使用插槽

如果使用单文件组件并使用 <template> 块声明 HTML,你可能会认为不能直接访问渲染函数,但事实并非如此。

当我第一次遇到这个问题时,我花了一些时间试图了解如何在渲染函数中移动插槽函数,但在Spa 之后,我想起了 <template>标签是由编译器为我们转化成渲染函数的。

了解 <template> 块和渲染函数是等价的,对我们定义解决问题的方法有很大帮助。事实上,为了消除警告并确保在我们的组件中跟踪依赖关系,我们需要确保插槽的调用发生在HTML中(随后被框架编译成一个渲染函数)。

举个例子:

// 缺点 - 如插槽改变,它将不会改变
<template>
  <div :class="{ 'style-for-svg': isSvg }">
    <slot></slot>
  </div>
</template>

<script>
import { ref } from 'vue'

export default {
  setup( props, { slots } ) {
    const isSvg = ref( false );

    if( slots.default()[0].type === 'svg' ) {
      isSvg.value = true;
    }

    return {
      isSvg
    }
  }
}
</script>


// 优点:插槽改变,跟着变化
<template>
  <div :class="{ 'style-for-svg': $slots.default()[0].type === 'svg' }">
    <slot></slot>
  </div>
</template>

<script>

export default {
  setup( ) {
  }
}
</script>

解决这个问题是很简单的。直接在模板中加入函数调用,就可以解决我们的问题了。不幸的是,上面的解决方案代码不够简洁。

那要怎么做呢?使用计算属性。

在调查过程中,计算属性也被编译为渲染函数的一部分,可以用来使代码更易读,并且仍然保持变量的响应式。


<template>
  <div :class="{ 'style-for-svg': isSvg }">
    <slot></slot>
  </div>
</template>

<script>
import { computed } from 'vue'

export default {
  setup( ) {

    const isSvg = computed( () => {
      return slots.default()[0].type === 'svg';
    } );

    return {
       isSvg
    }

  }
}
</script>

总结

在开发Vue组件时,需要访问插槽函数的情况并不常见,但如果你需要这样做,我希望上面的解决方案能为你节省一些时间。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8