Vue3渲染函数语法下默认插槽template ref始终为null问题求助
更新
事后看来,我不推荐这种实现方式。让插槽父组件(InfiniteScroll)通过props传递一个回调给子组件(List),在父组件中设置子组件的ref要简单得多,仅需在props中定义一个简单的回调。吃一堑长一智……
已解决!
请查看下方评论。
原始求助
请帮忙排查这个始终为null的ref值问题!我搭建了一个示例来展示问题。提前感谢🙏
问题概述
我有一对父子组件实现横向滚动列表,父组件处理无限滚动逻辑,内部的列表需要动态加载补充数据。目标是在父组件InfiniteScroll.vue中注册IntersectionObserver,获取子组件List.vue中列表末尾元素的template ref。理想状态是当terminalRef进入视口时,IntersectionObserver触发回调,获取并补充列表数据。
因此IntersectionObserver需要能够获取子组件的terminalRef.value。根据Vue文档,通过插槽结合render和expose方法可以实现这一点,但我找不到使用render语法通过插槽传递template ref的完整示例。经过多次尝试,大部分功能已正常,但InfiniteScroll.vue中获取到的子组件ref仍然为null。
注意:我已遵循Vue文档中为组件模板Ref类型化的指引。
代码实现
InfiniteScroll.vue
<script lang="ts"> import { ref, watch, onMounted, onBeforeUnmount, h, defineComponent } from 'vue' import List from '../List/List.vue' export default defineComponent({ components: { List }, emits: ['infinite'], setup(props, { slots, emit }) { const scrollContainer = ref<HTMLElement|null>(null) const ListInstance = ref<InstanceType<typeof List>|null>(null) const getTerminalRef = () => ListInstance.value?.$refs.terminalRef as HTMLDivElement|null const observer = new IntersectionObserver((entries) => { const entry = entries[0] if (entry.isIntersecting) { emit('infinite') } }, { root: scrollContainer.value, threshold: 0.9 }) onMounted(() => { if (ListInstance.value) { // 这里始终为null,但Vue开发者工具中能看到这个ref debugger // <---- 永远不会走到这里 const ref = ListInstance.value.$refs.terminalRef as HTMLDivElement if (ref) { observer.observe(ref) } } }) return () => h( 'div', { ref: scrollContainer, class: 'scroll-container' }, h('slot', { name: 'List', ref: 'ListInstance' }, slots.default && slots.default()) ) } }) </script>
List.vue
<script lang="ts"> import { ref, h, defineComponent, onMounted } from 'vue' import { type UIshow } from '../../client-types' import ListItem from './ListItem.vue' export default defineComponent({ components: { ListItem }, props: ['genre', 'shows'], setup({ genre, shows }: { genre: string, shows: Map<string, UIshow> }, { expose }) { const terminalRef = ref<HTMLDivElement|null>(null) expose({ terminalRef }) return () => h('div', { class: 'genre-row' }, { default: () => [ h('h2', { class: 'title' }, [genre]), h('ul', { class: 'primary' }, [ ...Array.from(shows).map( ([sortingName, show]) => { return h('li', { key: sortingName }, [ h(ListItem, { show }) ]) }), h('li', null, h('div', { ref: terminalRef }, '.')) ]) ]}) } }) </script>
Lists.vue(循环渲染多组InfiniteScroll/List)
<template> <template v-if="genreMap.size"> <div v-for="[ genre, shows ] of genreMap" :key="genre" class="all-lists"> <template v-if="shows.size"> <InfiniteScroll @infinite="loadPage"> <List :genre="genre" :shows="shows" /> </InfiniteScroll> </template> </div> </template> <template v-else> <h1 class="title">Loading...</h1> </template> </template>
开发者工具截图
可以看到Vue开发者工具显示InfiniteScroll组件能识别该ref:

一切看似正常,但List组件在开发者工具中无响应式属性显示:

内容的提问来源于stack exchange,提问作者Kraken
相关产品推荐
相关产品推荐

