Vue2迁移Vue3+TypeScript:组件监听事件重映射的类型问题如何解决
针对你迁移过程中遇到的两个核心问题(attrs无强类型、事件对象e类型为unknown),可参考以下三种可行方案:
方案1:快速类型断言(适合中小项目,开发效率最高)
通过显式类型声明绕过TS校验,代码改动最小:
type InputEventCallback = (e: Event, nodeId: string, index?: string) => void type Listeners = Record<string, (e: Event) => void> const inputPortListeners = computed(() => { return Object.keys(attrs).reduce<Listeners>((listeners, name: string) => { if (!name.startsWith('onInput')) return listeners // 断言attrs属性为合法事件回调类型 const listener = attrs[name] as unknown as InputEventCallback // 处理事件名首字母小写,和Vue2逻辑对齐:onInputChange => change const eventName = name.replace('onInput', '')[0].toLowerCase() + name.replace('onInput', '').slice(1) listeners[eventName] = (e: Event) => { // 断言事件目标为HTMLElement,可正常访问dataset const target = e.target as HTMLElement listener.call(this, e, props.node.id, target.dataset.index) } return listeners }, {}) })
方案2:类型守卫校验(适合严格TS规范的大型项目,安全性最高)
通过运行时类型校验避免类型断言带来的隐式风险,完全符合TS语法规范:
// 通用类型守卫 const isFunction = (val: unknown): val is Function => typeof val === 'function' const isHTMLElement = (val: unknown): val is HTMLElement => val instanceof HTMLElement type Listeners = Record<string, (e: Event) => void> const inputPortListeners = computed(() => { return Object.keys(attrs).reduce<Listeners>((listeners, name: string) => { if (!name.startsWith('onInput')) return listeners const rawListener = attrs[name] // 运行时校验属性是否为函数,避免异常值 if (!isFunction(rawListener)) return listeners const eventName = name.replace('onInput', '')[0].toLowerCase() + name.replace('onInput', '').slice(1) listeners[eventName] = (e: Event) => { // 运行时校验目标节点类型,避免空值/非元素节点报错 if (!isHTMLElement(e.target)) return rawListener(e, props.node.id, e.target.dataset.index) } return listeners }, {}) })
方案3:预定义attrs类型(适合组件接口固定的场景,类型提示最完善)
提前定义组件所有接收的事件类型,让TS自动推导attrs属性类型,无需额外断言即可获得完整类型提示:
// 定义组件所有input前缀事件的参数结构 type InputEvents = { add: [e: Event, nodeId: string, index: string] edit: [e: Event, nodeId: string, index: string] delete: [e: Event, nodeId: string, index: string] // 其他input开头的事件按需补充 } // 推导attrs对应的事件键名(自动加on前缀、首字母大写,和Vue3事件命名规则对齐) type ComponentAttrs = { [K in keyof InputEvents as `on${Capitalize<K>}`]: (...args: InputEvents[K]) => void } & Record<string, unknown> // 断言attrs为自定义类型 const attrs = useAttrs() as ComponentAttrs type Listeners = Record<string, (e: Event) => void> const inputPortListeners = computed(() => { return Object.keys(attrs).reduce<Listeners>((listeners, name: keyof ComponentAttrs) => { if (typeof name !== 'string' || !name.startsWith('onInput')) return listeners const eventName = name.replace('onInput', '').toLowerCase() listeners[eventName] = (e: Event) => { const target = e.target as HTMLElement attrs[name](e, props.node.id, target.dataset.index) } return listeners }, {}) })
额外注意点
Vue3组合式API的script setup语法下不存在组件实例this,如果你的事件回调依赖原组件的this上下文,需要通过getCurrentInstance()获取实例后绑定:
import { getCurrentInstance } from 'vue' const instance = getCurrentInstance()! // 调用回调时替换为实例代理对象 rawListener.call(instance.proxy, e, props.node.id, target.dataset.index)
内容的提问来源于stack exchange,提问作者CodeBreaker
相关产品推荐
相关产品推荐

