无法在 vue 3 组合中的组件上使用模板引用 api

can't use template ref on component in vue 3 composition api

我想从父级获取 vue.js 组件的尺寸(我正在使用实验性 script setup)。

当我在组件中使用 ref 时,它按预期工作。我得到尺寸:

// Child.vue
<template>
  <div ref="wrapper">
   // content ...
  </div>
</template>
<script setup>
import { ref, onMounted } from 'vue'

const wrapper = ref(null)

onMounted(() => {
  const rect = wrapper.value.getBoundingClientRect()
  console.log(rect) // works fine!
})
</script>

但是我想获取父组件内部的尺寸。这可能吗?

我试过这个:

// Parent.vue
<template>
  <Child ref="wrapper" />
</template>
<script setup>
import Child from './Child'
import { ref, onMounted } from 'vue'

const wrapper = ref(null)

onMounted(() => {
  const rect = wrapper.value.getBoundingClientRect()
  console.log(rect) // failed!
})
</script>

控制台记录此错误消息: Uncaught (in promise) TypeError: x.value.getBoundingClientRect is not a function


在文档中我只能找到使用方法 template refs inside the child component

这种方法是否不起作用,因为 refs 像 rfcs description says 一样“默认关闭”?

您可以使用如下所示的 $el 字段访问根元素:

<template>
  <Child ref="wrapper" />
</template>

<script setup>
import Child from './Child'
import { ref, onMounted } from 'vue'

const wrapper = ref(null)

onMounted(() => {
  const rect = wrapper.value.$el.getBoundingClientRect()
  console.log(rect) 
})
</script

我不认为这一定与 <script setup> 标签有关。即使在标准脚本语法中,您的第二个示例也不会按原样运行。

问题是您将 ref 直接放在子组件上:

<template>
  <Child ref="wrapper" />
</template>

并且对组件的引用 NOT 与对该组件的 root 元素的引用相同。它没有 getBoundingClientRect() 方法。

事实上,Vue 3 不再要求组件具有单个根元素。您可以将子组件定义为:

<template>
  <div ref="wrapper1">// content ...</div>
  <div ref="wrapper2">// content ...</div>
</template>
<script >
import { ref } from "vue";

export default {
  name: "Child",

  setup() {
    const wrapper1 = ref(null);
    const wrapper2 = ref(null);
 
    return { wrapper1, wrapper2 };
  },
};
</script>

现在您的父组件中的 ref 应该是什么?

从父组件将 wrapper.value 记录到您的控制台。它实际上是您的 Child 组件中所有 refs 的对象:

{
  wrapper1: {...}, // the 1st HTMLDivElement
  wrapper2: {...}  // the 2nd HTMLDivElement
}

你可以wrapper.value.wrapper1.getBoundingClientRect(),那会很好。

好的,所以这是您需要做的:

// Parent component
<template>
  <Child :get-ref="(el) => { wrapper = el }" />
</template>

<script setup>
import Child from './Child.vue';
import { ref, onMounted } from 'vue';

const wrapper = ref();

onMounted(() => {
  const rect = wrapper.value.getBoundingClientRect()
  console.log(rect) // works fine!
});
</script>

// Child component
<template>
  <div :ref="(el) => { wrapper = el; getRef(el)}">
   // content ...
  </div>
</template>

<script setup>
import { defineProps, ref, onMounted } from 'vue';
  
const props = defineProps({
  getRef: {
    type: Function,
  },
});

const wrapper = ref();

onMounted(() => {
  const rect = wrapper.value.getBoundingClientRect()
  console.log(rect) // works fine!
});
</script>

要了解原因,我们需要查看 ref 上的 Vue 文档: Vue special-attribute 'ref'.

关于(模板)ref 的动态绑定,它说:

<!-- When bound dynamically, we can define ref as a callback function,
passing the element or component instance explicitly -->
<child-component :ref="(el) => child = el"></child-component>

由于 prop 允许您将数据从父级传递给子级,我们可以结合使用 prop 和动态 ref 绑定来获得想要的结果。首先,我们 将动态 ref 回调函数作为 getRef prop:

传递给子
<Child :get-ref="(el) => { wrapper = el }" />

然后,子元素在元素上执行动态 ref 绑定,其中它 将目标 el 分配给它的 wrapper ref 并在该回调函数中调用 getRef prop 函数 让父级也获取 el:

<div :ref="(el) => {
            wrapper = el; // child registers wrapper ref
            getRef(el); // parent registers the wrapper ref
            }">

请注意,这允许我们在父 子组件中同时拥有 wrapper 元素的 ref。如果您希望在父组件中访问 wrapper 元素 只有 ,您可以跳过子组件的回调函数,只需将 ref 绑定到 prop 像这样:

// Child component
<template>
  <div :ref="getRef">
   // content ...
  </div>
</template>

<script setup>
import { defineProps } from 'vue';

const props = defineProps({
  getRef: {
    type: Function,
  },
});
</script>

那将只让父项拥有 ref 到您的模板的 wrapper

我今天 运行 进入这个问题。问题是,当使用 <script setup> 模式时,返回声明变量的 none。当您获得对组件的引用时,它只是一个空对象。解决这个问题的方法是在设置块中使用 defineExpose

// Child.vue

<template>
  <div ref="wrapper">
   <!-- content ... -->
  </div>
</template>

<script setup>
import { defineExpose, ref } from 'vue'

const wrapper = ref(null)

defineExpose({ wrapper })
</script>

您在父项中设置模板引用的方式很好。您在控制台中看到 empty object { } 意味着它正在运行。

就像已经说过的其他答案一样,可以像这样从父级访问子引用:wrapper.value.wrapper.getBoundingClientRect()

rfc 有一节讨论 how/why 这有效:https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md#exposing-components-public-interface

同样重要的是要注意,使用 <script setup> 模式,您在父组件中的引用将不是 ComponentInstance。这意味着您不能像其他方式那样调用 $el 。它只会包含您在 defineExpose.

中输入的值

如果您将 wrapper.value 视为 null,那么请确保您尝试获取 ref 的元素未隐藏在错误的 [=13] 下=].在实际需要该元素之前,Vue 不会实例化 ref

我知道这个答案不是针对当前问题的,但它是“template ref null vue 3 composition api”的最佳结果所以我怀疑更多像我一样的人会来这里并且会欣赏这个诊断.