Vue 3 中 ref 获取 scrollHeight 属性为 undefined 问题定位

本文共五部分,问题出现场景、定位问题、思考原因、问题解决、总结。 定位到ref获取到Vue 组件实例的代理(Proxy)对象问题。

1. 问题出现场景

vue3使用Splitpanes组件,实现可拖拽分割窗口功能时,需要在Pane组件上,实现滚动到底部效果。

在开发过程中发现,在pane组件上使用ref获取组件属性,获取不到scrollHeight,值为undefined。

html代码如下:

html 复制代码
     <Splitpanes class="flex flex-row h-full default-theme">
        <Pane
          v-for="(item, index) in models"
          ref="scrollRef"
          :key="index"
          :class="[isMobile ? 'p-2' : 'p-4']"
          class="h-full w-1/2 m-auto dark:bg-[#101014] overflow-hidden overflow-y-auto"
        >
          <div id="image-wrapper" class="relative">
            <Card
              :src-url="item.avatar"
              :avatar-type="item.type" :data-sources="modelChat(item.type, +uuid)" :loading="loading"
              @scroll-update="scrollUpdate" @handle-stop="handleStop" @handle-delete="handleDelete"
              @on-regenerate="onRegenerate"
            />
          </div>
        </Pane>
      </Splitpanes>

滑轮滚动到底部js代码如下:

js 复制代码
  const scrollToBottom = async () => {
    // 等待下一个 DOM 更新周期,确保 DOM 已经渲染完成
    await nextTick()
    console.warn('scrollRef:', scrollRef.value)
    // 处理多个滚动容器的情况(数组形式)
    if (scrollRef.value && Array.isArray(scrollRef.value)) {
      scrollRef.value.forEach((item) => {
        // 设置滚动位置为滚动容器的总高度,即滚动到底部
        console.warn('scrollHeight:', item.scrollHeight)
        item.scrollTop = item.scrollHeight
      })
    }
    // 处理单个滚动容器的情况
    else if (scrollRef.value) {
      scrollRef.value.scrollTop = scrollRef.value.scrollHeight
    }
  }

2.定位问题

使用浏览器打印节点,查看属性:

可以看出,ref获取到Vue 组件实例的代理(Proxy)对象,【在 Vue 3 中,组件实例被包装在 Proxy 中以实现响应式】, 每个组件实例上都有一些属性,例如:

  • $i18n:国际化相关的对象
  • $el:组件根 DOM 元素的引用(目前是省略状态,可能需要展开才能看到具体值)
  • $attrs:组件的属性
  • $props:组件的 props
  • $emit:触发事件的方法
  • 以及一些其他 Vue 内部属性和方法

实例上没有scrollHeight属性。

3. 思考原因

scrollHeight 是 DOM 元素的属性,而不是 Vue 组件实例的属性。当你通过 ref 获取第三方组件时,你得到的是组件实例,而不是底层的 DOM 元素。

对比说明:

属性 所属对象 说明
scrollTop 组件实例/ DOM 元素 组件可能暴露了这个属性
scrollHeight DOM 元素 原生 DOM 属性,不会自动暴露

4. 问题解决

对ref获取的节点,取$el属性,获取scrollHeight值,所以修改js代码为:

js 复制代码
  const scrollRef = ref<ComponentPublicInstance>()

  /**
   * 滚动到底部
   * 将滚动条滚动到容器的最底部
   * 适用于聊天场景,当有新消息时自动滚动到底部
   */
  const scrollToBottom = async () => {
    // 等待下一个 DOM 更新周期,确保 DOM 已经渲染完成
    await nextTick()
    // 处理多个滚动容器的情况(数组形式)
    if (scrollRef.value && Array.isArray(scrollRef.value)) {
      scrollRef.value.forEach((item) => {
        // 设置滚动位置为滚动容器的总高度,即滚动到底部
        if (item.$el) {
          item.$el.scrollTop = item.$el.scrollHeight
        }
      })
    }
    // 处理单个滚动容器的情况
    else if (scrollRef.value && scrollRef.value.$el) {
      const documentElement = scrollRef.value.$el
      documentElement.scrollTop = documentElement.scrollHeight
    }
  }

5. 总结

使用vue3提供的方法时,确定函数返回的对象类型很重要。

以上是手动写的hooks函数,也可以使用函数 | VueUse 工具库, vue成熟的hooks库,里面实现的scroll相关函数。

相关推荐
Dontla6 小时前
(临时解决)Chrome调试避免跳入第三方源码(设置Blackbox Scripts、将目录添加到忽略列表、向忽略列表添加脚本)
前端·chrome
我的div丢了肿么办6 小时前
js函数声明和函数表达式的理解
前端·javascript·vue.js
AAA阿giao6 小时前
JavaScript 对象字面量与代理模式:用“胡巴送花”讲透面向对象与设计思想
javascript
云中雾丽6 小时前
React.forwardRef 实战代码示例
前端
朝歌青年说6 小时前
一个在多年的技术债项目中写出来的miniHMR热更新工具
前端
高台树色6 小时前
终于记住Javascript垃圾回收机制
javascript
Moonbit6 小时前
倒计时 2 天|Meetup 议题已公开,Copilot 月卡等你来拿!
前端·后端
Glink6 小时前
现在开始将Github作为数据库
前端·算法·github
小仙女喂得猪6 小时前
2025 跨平台方案KMP,Flutter,RN之间的一些对比
android·前端·kotlin