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

TypeScript中可选_id属性引发函数参数类型不兼容问题

说明:TypeClass是项目中处理的一种对象文档,实际称为typeclass,并非TypeScript中的type或class,特此说明避免混淆。

问题背景

我开发了一个支持船舶TypeClass(类似汽车车型)新建和编辑的组件:当TypeClass包含_id时代表已有文档,需要调用接口更新数据库;无_id时仅更新本地状态。

相关核心代码如下:

const handleUpdateDeck = async (updatedDeck: FixedDeck | HoistableDeck) => {
  const updatedTypeClass = getUpdatedTypeClassWithNewDeck(
    typeClass,
    updatedDeck,
    selectedZoneID
  );

  if (updatedTypeClass._id && "_id" in updatedTypeClass) {
    try {
      await handleUpdateTypeClass(updatedTypeClass);
    } catch (err: any) {
      console.log("Updating deck failed: ", err.message);
    }
  } else {
    setTypeClass(updatedTypeClass);
  }
};

getUpdatedTypeClassWithNewDeck负责更新TypeClass中的deck数据,返回对象可能包含或不包含_id:

export default function (
  typeClass: Omit<TypeClass, "_id"> & { _id?: string },
  updatedDeck: FixedDeck | HoistableDeck,
  selectedZoneID: string
) {
  return {
    ...typeClass,
    zones: typeClass.zones.map((zone) => {
      if (zone._id === selectedZoneID) {
        const zoneToUpdate: Zone | undefined = typeClass.zones.find(
          (z) => z._id === selectedZoneID
        );

        if (!zoneToUpdate) {
          throw new Error("failed to find zone to update");
        }
        const updatedZone: Zone = {
          ...zoneToUpdate,
          decks: zoneToUpdate.decks.map((deck) =>
            deck._id === updatedDeck._id ? updatedDeck : deck
          ),
        };
        return updatedZone;
      } else {
        return zone;
      }
    }),
  };
}

handleUpdateTypeClass仅接收包含必填_id的TypeClass类型:

const handleUpdateTypeClass = async (tc: TypeClass) => {
  try {
    if (!updateTypeClass) {
      throw new Error("Update function undefined");
    }
    if (!tc) {
      throw new Error("Couldn't find type-class");
    }

    if (tc.name.length === 0) {
      throw new Error("name|Can't leave name empty");
    }

    if (tc.zones.length === 0) {
      throw new Error("zones|Type-classes must have at least one zone");
    }

    setErrors({});
    setIsSubmitting(true);

    await updateTypeClass(tc);
    setTypeClass(tc);
  } catch (err: any) {
    throw new Error(err.message);
  } finally {
    setIsSubmitting(false);
  }
};

TypeClass的基础类型定义:

export type TypeClass = {
  _id: string;

  // TypeClass名称
  name: string;

  // 包含的区域列表
  zones: Zone[];
};

export type Zone = {
  _id: string;

  // 区域名称
  name: string;

  // 区域内的甲板列表(包含固定底层甲板,最少1个)
  decks: (FixedDeck | HoistableDeck)[];
};

组件中使用的TypeClass类型为:

typeClass: Omit<TypeClass, "_id"> & { _id?: string };

遇到的问题

我已经通过updatedTypeClass._id && "_id" in updatedTypeClass判断_id存在,但调用handleUpdateTypeClass时仍然触发类型错误:

Types of property '_id' are incompatible.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.ts(2345)

解决方法

方法1:直接使用类型断言

最简单的方式是在调用时断言updatedTypeClass为TypeClass类型:

await handleUpdateTypeClass(updatedTypeClass as TypeClass);

注意:这种方式需要确保运行时_id确实存在,否则可能引发运行时错误。

方法2:自定义类型守卫函数

编写类型守卫明确判断对象是否符合TypeClass类型,让TypeScript完全信任判断结果:

function isTypeClass(tc: Omit<TypeClass, "_id"> & { _id?: string }): tc is TypeClass {
  return typeof tc._id === 'string' && tc._id.length > 0;
}

修改判断逻辑为:

if (isTypeClass(updatedTypeClass)) {
  try {
    await handleUpdateTypeClass(updatedTypeClass);
  } catch (err: any) {
    console.log("Updating deck failed: ", err.message);
  }
} else {
  setTypeClass(updatedTypeClass);
}

这种方式兼顾类型安全和运行时校验,是更推荐的方案。

方法3:优化工具函数的返回类型

给getUpdatedTypeClassWithNewDeck添加泛型,让TypeScript根据输入自动推断返回类型:

export default function getUpdatedTypeClassWithNewDeck<T extends Omit<TypeClass, "_id"> & { _id?: string }>(
  typeClass: T,
  updatedDeck: FixedDeck | HoistableDeck,
  selectedZoneID: string
): T {
  return {
    ...typeClass,
    zones: typeClass.zones.map((zone) => {
      if (zone._id === selectedZoneID) {
        const zoneToUpdate: Zone | undefined = typeClass.zones.find(
          (z) => z._id === selectedZoneID
        );

        if (!zoneToUpdate) {
          throw new Error("failed to find zone to update");
        }
        const updatedZone: Zone = {
          ...zoneToUpdate,
          decks: zoneToUpdate.decks.map((deck) =>
            deck._id === updatedDeck._id ? updatedDeck : deck
          ),
        };
        return updatedZone;
      } else {
        return zone;
      }
    }),
  };
}

此时如果输入的typeClass包含_id,返回的updatedTypeClass会被自动推断为TypeClass类型,无需额外判断即可直接调用handleUpdateTypeClass。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 20:32:49