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

如何定义TypeScript中拆分对象为互补子对象的正确类型?

TypeScript 对象拆分的类型定义问题

问题描述

在TypeScript中,如何定义通用类型,实现将一个对象拆分为两个包含互补键的子对象?比如把 {x: "x", y: "y", z:"z"} 这类对象拆成 {a: {x: "x"}, b: {y: "y", z:"z"}} 的结构。针对通用Record类型,该如何定义拆分后的类型?

我尝试了以下代码,但出现了错误:

type Split<Union extends Record<string, string | number>>= <A extends Partial<Union>>() => {
    a: A,
    b: Omit<Union, keyof A>
};

type SpecificUnion = {
    x: string
    y: number
    z: string
}

const split: Split<SpecificUnion> = () => {
    return {
        a: { x: "x" },
        b: { y: "y", z: "z"}
}}

错误信息如下:

Type '() => { a: { x: string; }; b: { y: string; z: string; }; }' is not assignable to type 'Split<SpecificUnion>'.
  Call signature return types '{ a: { x: string; }; b: { y: string; z: string; }; }' and '{ a: A; b: Omit<SpecificUnion, keyof A>; }' are incompatible.
    The types of 'a' are incompatible between these types.
      Type '{ x: string; }' is not assignable to type 'A'.
        '{ x: string; }' is assignable to the constraint of type 'A', but 'A' could be instantiated with a different subtype of constraint 'Partial<SpecificUnion>'.

这个错误看起来矛盾:它既说 '{ x: string; }' 不能赋值给类型A,又说它符合A的约束条件。

错误原因分析

你当前的 Split 类型定义了一个泛型函数,泛型参数 A 的控制权在调用方,而非函数返回值。这意味着 Split<SpecificUnion> 要求函数能返回任意符合 Partial<SpecificUnion> 的 A 对应的结构,但你的函数只能返回固定的 a: {x: string} 和对应 b,类型自然不兼容。

简单说,你搞反了泛型的位置:应该让 Split 类型本身接收表示第一个子对象键集合的泛型参数,而非让函数成为泛型。

正确的类型定义方案

方案1:基于键集拆分的通用类型

定义泛型类型 Split<T, K extends keyof T>,其中 T 是原对象类型,K 是要放入 a 中的键集合:

type Split<T, K extends keyof T> = {
  a: Pick<T, K>;
  b: Omit<T, K>;
};

使用时明确指定拆分的键:

type SpecificUnion = {
  x: string;
  y: number;
  z: string;
};

// 将x拆分到a,剩余键放到b
const splitResult: Split<SpecificUnion, "x"> = {
  a: { x: "x" },
  b: { y: 123, z: "z" } // 注意:原代码中y赋值为字符串"y"不符合类型定义,需改为number类型
};

方案2:支持函数返回的泛型类型

如果需要用函数返回拆分后的对象,把泛型参数放到函数上:

type SplitFn<T> = <K extends keyof T>(a: Pick<T, K>, b: Omit<T, K>) => { a: Pick<T, K>, b: Omit<T, K> };

const split: SplitFn<SpecificUnion> = (a, b) => ({ a, b });

const result = split({ x: "x" }, { y: 456, z: "z" });
// result类型:{ a: Pick<SpecificUnion, "x">, b: Omit<SpecificUnion, "x"> }

更灵活的自动推导版本:

function split<T, K extends keyof T>(obj: T, keys: K[]): Split<T, K> {
  const a = {} as Pick<T, K>;
  const b = {} as Omit<T, K>;
  
  for (const key in obj) {
    if (keys.includes(key as K)) {
      a[key as K] = obj[key];
    } else {
      b[key as Exclude<keyof T, K>] = obj[key];
    }
  }
  
  return { a, b };
}

const obj = { x: "x", y: 789, z: "z" };
const splitResult = split(obj, ["x"]);
// splitResult.a: {x: string},splitResult.b: {y: number, z: string}

原代码报错的深层原因

Split<SpecificUnion> 是一个泛型函数类型,它要求函数能应对任意 A extends Partial<SpecificUnion> 的情况——比如调用时指定 A 为 {y: number},函数就需要返回 a: {y: number}, b: {x: string, z: string},但你的函数只能返回固定结构,因此TypeScript判定类型不兼容。

错误信息里的“可赋值给约束”是指 {x: string} 符合 Partial<SpecificUnion> 的约束,但“A可能被实例化为约束的不同子类型”(比如 {y: number}),此时函数返回的 a 不符合要求,所以报错。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 10:10:31