You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:
    Vue开发者工具显示InfiniteScroll的ref

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

内容的提问来源于stack exchange,提问作者Kraken

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 09:58:13