将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>
注意事项
- 先安装依赖:
npm install mitt或yarn add mitt - 原代码中
this.$el.querySelector("input").innerHTML = "#scroll-sync-template"属于无效代码,已移除(模板中无input元素) - 若项目中已有全局事件总线方案,可替换掉mitt的使用
- 如果顶层组件是明确的,建议用
provide/inject替代实例遍历,逻辑更可靠
内容的提问来源于stack exchange,提问作者tsiPlus
相关产品推荐
相关产品推荐

