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

问题描述 投票:0回答:7

我想从父组件获取 vue.js 组件的尺寸(我正在使用实验性的脚本设置)。

当我在组件内使用 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
在子组件内部

这种方法是否不起作用,因为参考文献“默认关闭”,如 rfcs 描述所示

vue.js vue-component vuejs3 ref vue-composition-api
7个回答
59
投票

我今天遇到了这个问题。问题是,当使用

<script setup>
模式时,不会返回任何声明的变量。当您获得对组件的引用时,它只是一个空对象。解决这个问题的方法是在设置块中使用
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 有一节讨论了它的工作原理:https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md#exusing-components-public-interface

还需要注意的是,使用

<script setup>
模式,父组件中的 ref 将不是 ComponentInstance。这意味着您不能像其他方式那样调用
$el
。它只会包含您在
defineExpose
中输入的值。


12
投票

我不认为这一定与

<script setup>
标签有关。即使在标准脚本语法中,您的第二个示例也无法按原样工作。

问题是您将

ref
直接放在子组件上:

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

并且对组件的引用与对该组件的 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
从父组件记录到控制台。它实际上是子组件中所有引用的对象:

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

你可以这样做

wrapper.value.wrapper1.getBoundingClientRect()
,效果很好。


7
投票

您可以使用

$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

6
投票

如果您将

wrapper.value
视为
null
,请确保您尝试获取
ref
的元素没有隐藏在错误的
v-if
下。在实际需要该元素之前,Vue 不会实例化
ref

我意识到这个答案不适用于当前的问题,但它是“template ref null vue 3 Composition api”的最佳结果,所以我怀疑更多像我这样的人会来到这里并欣赏这个诊断。


3
投票

好吧,这就是你需要做的:

// 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>

要了解原因,我们需要查看 Vue 的文档

ref
Vue 特殊属性“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
 回调函数传递给 child
作为
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
            }">

请注意,这允许我们在父组件和子组件中同时拥有 
ref

元素的

wrapper
。如果您希望仅访问父组件中的
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> const elementRef = ref(null) const getElementRect = () => { return elementRef.value.getBoundingClientRect() } defineExpose({ getElementRect }) </script> <template> <div ref="elementRef"> <!-- content ... --> </div> </template>

2
投票

ref="foo"
附加组件实例而不是 HTML 元素实例。如果您的组件有根元素,您可以使用这些可组合项:

0
投票
export const maybeGetHTMLElement = (elem: Element | ComponentPublicInstance | null): HTMLElement | null => { if (elem == null) { return null; } if (elem instanceof Element) { if (elem instanceof HTMLElement) { return elem; } } else if (elem.$el instanceof HTMLElement) { return elem.$el } return null; }; export const useMaybeComponentRef = (element: Ref<Element | ComponentPublicInstance | null>): ComputedRef<HTMLElement | null> => { const res = computed(() => maybeGetHTMLElement(element.value)); return res; };

滚动视图示例:

<template> <InfiniteScroll class="container" ref="scrollContainerRef" :refs="messageRefs" :reverse="true" :offset="offset" @load-more="loadMore" /> </template> <script setup lang="ts"> import { onMounted, ref } from 'vue'; import { useMaybeComponentRef } from "@/foo"; const scrollContainerRef = ref<HTMLElement | null>(null); const scrollContainer = useMaybeComponentRef(scrollContainerRef); onMounted(() => console.log(scrollContainer.value.scrollHeight)); </script>

scrollContainer.value

将解析为对
InfiniteScroll
容器的 HTML 根节点的引用。

	
© www.soinside.com 2019 - 2024. All rights reserved.