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

TypeScript中Omit工具类型异常:属性变any且未触发预期错误

TypeScript泛型类型修改问题

场景一:移除Context接口的one属性并透传泛型

以下是复现代码及预期行为注释:

/*******************
 * First Situation *
 *******************/

type DefaultContext = Record<string, any>;

const logger = (str?: string | Record<string, unknown> | any[]) =>
  console.log(str);

interface Context<T extends Record<string, unknown> = Record<string, unknown>>
  extends DefaultContext {
  one?: T;
  many?: T[];
}

function oldProcess<
  T extends Record<string, unknown> = Record<string, unknown>
>(context: Context<T>) {
  const many = context.many; // T[] | undefined
  logger(many);

  const one = context.one; // T | undefined
  logger(one);
}

type BadContext<T extends Record<string, unknown> = Record<string, unknown>> =
  Omit<Context<T>, "one">;

function badProcess<
  T extends Record<string, unknown> = Record<string, unknown>
>(context: BadContext<T>) {
  const many = context.many; // expected `T[] | undefined` received `any`
  logger(many);

  const one = context.one; // expected error received `any` ❌
  logger(one); // expected error ❌
}

type AsiContext<T extends Record<string, unknown> = Record<string, unknown>> =
  Context<T> & { one: never };

function asiProcess<
  T extends Record<string, unknown> = Record<string, unknown>
>(context: AsiContext<T>) {
  const many = context.many; // T[] | undefined
  logger(many);

  const one = context.one; // never 😐
  logger(one); // want error ❌
}

问题:

  • 使用Omit创建的BadContext将所有属性转为any类型,且仍允许访问已移除的one属性,不符合预期。
  • 使用交叉类型{one: never}创建的AsiContext虽保留了原有属性类型,但传递never类型的one时未触发预期错误。

场景二:移除TArgs类型的uid属性、透传泛型并将uids设为必填

复现代码及预期行为注释:

/********************
 * Second Situation *
 ********************/

type NodeObj = Record<string, unknown> & {
  uid?: string;
  type?: string;
};

type Entity<T extends Record<string, unknown> = Record<string, unknown>> =
  NodeObj &
    T & {
      changed?: string;
      created?: string;
    };

type TArgs<E extends Entity = Entity> = Partial<E> & { uids?: string[] };
const foo: TArgs = {
  uid: undefined,
  type: "User",
  uids: ["0x2a", "0x2b"],
};

// Create a new Type that should keep TArgs typed but Omit `uid`
type BadTArgs<T extends TArgs = TArgs> = Omit<T & { uids: string[] }, "uid">;
const bar: BadTArgs = {
  uid: 42, // expected error as prop is now omitted ❌
  type: 42, // expected error as prop should be `string` ❌
  uids: 42, // expected error as ids should be `string[]` ❌
};

// Create a new Type that should keep TArgs typed but force `uid` to be `never`
type AsiTArgs<T extends TArgs = TArgs> = T & { uids: string[] } & {
  uid?: never;
};
const baz: AsiTArgs = {
  uid: undefined, // want error ❌
  type: "User", // ✅
  uids: ["0x1", "0x2"], // ✅
};

const fooType = foo.type; // string | undefined ✅
const barType = bar.type; // unknown ❌
const bazType = baz.type; // string | undefined ✅

const fooUid = foo.uid; // string | undefined ✅
const barUid = bar.uid; // unknown  ❌ expected error
const bazUid = baz.uid; // undefined ❌ expected never

问题:

  • 使用Omit创建的BadTArgs丢失了原有类型约束,所有属性类型异常,且允许设置已移除的uid属性。
  • 使用交叉类型{uid?: never}创建的AsiTArgs虽保留了其他属性类型,但允许设置uid: undefined,未触发预期错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 18:11:59