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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 11:55:47