Vue3 模板引用通过默认槽始终为 null(渲染函数语法)

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

解决了!请参阅下面的评论。

请帮我调试这个始终为空的引用值!我将这个codesandbox放在一起来帮助说明问题。蒂亚🙏

快速总结

我在一对父/子组件中有一个水平滚动行。父级正在处理无限滚动,其中有一个需要补充的列表。目标是在

InfiniteScroll.vue
(父级)中注册 IntersectionObserver,并检索放置在
List.vue
(子级)中终端列表元素之后的模板引用。理想情况下,当
terminalRef
出现在 DOM 中时,IntersectionObserver 会触发它的回调,获取并重新填充列表。

因此 IntersectionObserver 应该能够检索孩子的

terminalRef.value
。根据 vue 文档,这可以通过使用渲染和暴露方法的插槽来实现,但我正在努力寻找一个综合示例,该示例结合了使用
render
语法通过插槽传递模板引用。经过一番摆弄,我已经得到了大部分功能,但来自
List.vue
的子引用在
InfiniteScroll.vue
中仍然为空。

注意:我按照文档中的说明进行操作 键入组件模板参考

这是代码:

// InfinitScroll.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) { // always null, but the ref is visible in vue dev tools
          debugger // <---- Never stops here
          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

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 iterates over multiple InfiniteScroll/List pairs

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

Here you can see vue dev tools shows that InfiniteScroll component is aware of the ref:

Everything appears normal except the List component shows no reactive properties in dev tools

typescript vuejs3 infinite-scroll intersection-observer vuejs-slots
1个回答
0
投票

您需要定义

ListInstance
引用,因此在 InfinitScroll.vue 中,而不是:

h('slot', { name: 'List', ref: 'terminalRef' },

应该是

h('slot', { name: 'List', ref: ListInstance },

作为一个对象,而不是字符串,正如您在评论中所说。

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