Vue Ref属性类型推断异常:组合式函数添加isActive属性报错
Vue组合式函数useActiveItems的类型错误解决
我想给Vue组合式函数useActiveItems添加额外的isActive属性,但出现类型错误:property 'isActive' does not exist on type '{ id: string; text: string; }'。
原问题代码
import { ref, type Ref } from 'vue'; type ActiveItemType = { text: string; isActive?: boolean; }; export const useActiveItems = <T extends ActiveItemType>(data: T[]) => { let active = ''; function activeMapper(d: T) { return { ...d, isActive: d.text === active, }; } const itemsData = data.map(activeMapper); const items = ref(itemsData) as Ref<T[]>; function onSetActive(text: string) { active = text; items.value = items.value.map(activeMapper); } // initial set first one if (items.value[0]) { items.value[0].isActive = true; } return { items, onSetActive, }; }; const { items } = useActiveItems([ { id: '1', text: 't1' }, { id: '2', text: 't2' }, ]); if (items.value[0]) { items.value[0].isActive; // ERROR }
问题原因
原代码的泛型约束T extends ActiveItemType中,isActive是可选属性。传入的数组元素没有isActive,TypeScript推断出的T不包含该属性。虽然activeMapper给每个元素添加了isActive,但你把items断言成Ref<T[]>,直接丢失了新增属性的类型信息,导致外部访问时触发类型错误。
我的尝试
我在函数内部定义了以下类型,能解决问题,但不确定这是不是正确方式,还是应该仅在函数头部的泛型中处理?
type U = T & ActiveItemType; const items = ref(itemsData) as Ref<U[]>;
正确解决方案
你的思路是可行的,还可以进一步优化类型定义,让类型更明确:
方案一:明确必填的isActive属性
import { ref, type Ref } from 'vue'; type ActiveItemBase = { text: string; }; export const useActiveItems = <T extends ActiveItemBase>(data: T[]) => { // 定义包含激活状态的项类型:原数据类型 + 必填的isActive type ItemWithActive = T & { isActive: boolean }; let active = ''; function activeMapper(d: T): ItemWithActive { return { ...d, isActive: d.text === active, }; } const itemsData = data.map(activeMapper); const items = ref(itemsData) as Ref<ItemWithActive[]>; function onSetActive(text: string) { active = text; items.value = items.value.map(item => activeMapper(item)); } // 初始化第一个项为激活状态,同步active变量避免状态不一致 if (items.value[0]) { items.value[0].isActive = true; active = items.value[0].text; } return { items, onSetActive, }; }; const { items } = useActiveItems([ { id: '1', text: 't1' }, { id: '2', text: 't2' }, ]); if (items.value[0]) { items.value[0].isActive; // 类型正确,无错误 }
方案二:基于原类型扩展(兼容你的尝试)
如果想保留原ActiveItemType的定义,可以调整泛型约束,排除可选的isActive,再合并为必填属性:
import { ref, type Ref } from 'vue'; type ActiveItemType = { text: string; isActive?: boolean; }; export const useActiveItems = <T extends Omit<ActiveItemType, 'isActive'>>(data: T[]) => { type ItemWithActive = T & Required<Pick<ActiveItemType, 'isActive'>>; let active = ''; function activeMapper(d: T): ItemWithActive { return { ...d, isActive: d.text === active, }; } const itemsData = data.map(activeMapper); const items = ref(itemsData) as Ref<ItemWithActive[]>; function onSetActive(text: string) { active = text; items.value = items.value.map(item => activeMapper(item)); } if (items.value[0]) { items.value[0].isActive = true; active = items.value[0].text; } return { items, onSetActive, }; };
总结
你之前尝试的内部类型合并方式是合理的,不需要在函数头部泛型做过度复杂的处理。核心是要明确:我们返回的数组元素是原数据类型 + 必选的isActive属性,通过在函数内部定义这个合并类型,可以准确给items标注类型,解决外部访问的类型错误。
内容的提问来源于stack exchange,提问作者coure2011
相关产品推荐
相关产品推荐

