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

将Vue2 JavaScript选项式API组件升级为Vue3 TypeScript方案咨询

Vue2 JavaScript 组件升级到 Vue3 TypeScript 方案

升级核心调整点

  • 移除 Vue2 实例上的 $on/$off 事件 API,改用第三方事件库 mitt 实现跨组件滚动同步
  • 替换 $parent 遍历逻辑,用 Vue3 提供的组件实例方法获取顶层组件
  • 补充 TypeScript 类型定义,明确变量、函数参数及事件类型
  • 用模板引用 ref 替代直接操作 $el,符合 Vue3 最佳实践
  • 添加卸载钩子清理事件监听,避免内存泄漏

方案一:选项式API(贴近原代码结构)

<script lang="ts">
import { defineComponent, getCurrentInstance, onUnmounted } from 'vue';
import mitt from 'mitt';

// 模块级UUID生成器
let uuid = 0;
// 定义滚动同步事件的类型
type ScrollSyncData = {
  scrollTop: number;
  scrollHeight: number;
  clientHeight: number;
  barHeight: number;
  emitter: string;
};
// 创建全局事件总线
const emitter = mitt<{ 'scroll-sync': ScrollSyncData }>();

export default defineComponent({
  data() {
    return {
      topNode: null as null | ReturnType<typeof getCurrentInstance>,
      uuid: '' as string,
      containerRef: null as null | HTMLDivElement,
      scrollSyncHandler: null as null | ((data: ScrollSyncData) => void)
    };
  },
  beforeCreate() {
    this.uuid = uuid.toString();
    uuid += 1;
  },
  mounted() {
    // 遍历找到顶层组件实例
    let instance = getCurrentInstance();
    while (instance?.parent) {
      instance = instance.parent;
    }
    this.topNode = instance;

    // 滚动同步事件处理逻辑
    this.scrollSyncHandler = (data) => {
      if (data.emitter === this.uuid) return;
      const { scrollTop, scrollHeight, clientHeight, barHeight } = data;
      const scrollTopOffset = scrollHeight - clientHeight;

      if (!this.containerRef) return;
      this.containerRef.onscroll = null;
      if (scrollTopOffset > barHeight) {
        this.containerRef.scrollTop = scrollTop;
      }
      window.requestAnimationFrame(() => {
        this.containerRef!.onscroll = this.handleScroll;
      });
    };

    emitter.on('scroll-sync', this.scrollSyncHandler);
    // 绑定自身滚动事件
    this.containerRef?.addEventListener('scroll', this.handleScroll);
  },
  unmounted() {
    // 清理事件监听
    if (this.scrollSyncHandler) {
      emitter.off('scroll-sync', this.scrollSyncHandler);
    }
    this.containerRef?.removeEventListener('scroll', this.handleScroll);
  },
  methods: {
    handleScroll(e: Event) {
      const target = e.target as HTMLDivElement;
      const { scrollTop, scrollHeight, clientHeight, offsetHeight } = target;
      emitter.emit('scroll-sync', {
        scrollTop,
        scrollHeight,
        clientHeight,
        barHeight: offsetHeight - clientHeight,
        emitter: this.uuid
      });
    }
  }
});
</script>

<template>
  <div class="scroll-sync-container" ref="containerRef">
    <slot></slot>
  </div>
</template>

<style>
.scroll-sync-container {
  height: 100%;
  width: 100%;
  position: relative;
  overflow: auto;
}
</style>

方案二:组合式API(Vue3推荐写法)

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import mitt from 'mitt';

// 模块级UUID生成器
let uuid = 0;
// 定义滚动同步事件类型
type ScrollSyncData = {
  scrollTop: number;
  scrollHeight: number;
  clientHeight: number;
  barHeight: number;
  emitter: string;
};
// 创建全局事件总线
const emitter = mitt<{ 'scroll-sync': ScrollSyncData }>();

// 模板引用
const containerRef = ref<HTMLDivElement | null>(null);
// 当前组件UUID
const componentUuid = uuid.toString();
uuid += 1;

onMounted(() => {
  if (!containerRef.value) return;

  // 滚动同步处理函数
  const handleScrollSync = (data: ScrollSyncData) => {
    if (data.emitter === componentUuid) return;
    const { scrollTop, scrollHeight, clientHeight, barHeight } = data;
    const scrollTopOffset = scrollHeight - clientHeight;

    containerRef.value!.onscroll = null;
    if (scrollTopOffset > barHeight) {
      containerRef.value!.scrollTop = scrollTop;
    }
    window.requestAnimationFrame(() => {
      containerRef.value!.onscroll = handleScroll;
    });
  };

  // 自身滚动处理函数
  const handleScroll = (e: Event) => {
    const target = e.target as HTMLDivElement;
    const { scrollTop, scrollHeight, clientHeight, offsetHeight } = target;
    emitter.emit('scroll-sync', {
      scrollTop,
      scrollHeight,
      clientHeight,
      barHeight: offsetHeight - clientHeight,
      emitter: componentUuid
    });
  };

  // 绑定事件
  emitter.on('scroll-sync', handleScrollSync);
  containerRef.value.addEventListener('scroll', handleScroll);

  // 卸载时清理
  onUnmounted(() => {
    emitter.off('scroll-sync', handleScrollSync);
    containerRef.value?.removeEventListener('scroll', handleScroll);
  });
});
</script>

<template>
  <div class="scroll-sync-container" ref="containerRef">
    <slot></slot>
  </div>
</template>

<style>
.scroll-sync-container {
  height: 100%;
  width: 100%;
  position: relative;
  overflow: auto;
}
</style>

注意事项

  1. 先安装依赖:npm install mitt 或 yarn add mitt
  2. 原代码中 this.$el.querySelector("input").innerHTML = "#scroll-sync-template" 属于无效代码,已移除(模板中无input元素)
  3. 若项目中已有全局事件总线方案,可替换掉mitt的使用
  4. 如果顶层组件是明确的,建议用 provide/inject 替代实例遍历,逻辑更可靠

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 23:23:18