我想从父组件获取 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 描述所示?
我今天遇到了这个问题。问题是,当使用
<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
中输入的值。
我不认为这一定与
<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()
,效果很好。
您可以使用
$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
如果您将
wrapper.value
视为 null
,请确保您尝试获取 ref
的元素没有隐藏在错误的 v-if
下。在实际需要该元素之前,Vue 不会实例化 ref
。
我意识到这个答案不适用于当前的问题,但它是“template ref null vue 3 Composition api”的最佳结果,所以我怀疑更多像我这样的人会来到这里并欣赏这个诊断。
好吧,这就是你需要做的:
// 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>
ref="foo"
附加组件实例而不是 HTML 元素实例。如果您的组件有根元素,您可以使用这些可组合项: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 根节点的引用。