如何在不触及原始类型的情况下将类型合并回TypeScript

fxnxkyjh  于 2023-02-05  发布在  TypeScript
关注(0)|答案(4)|浏览(116)

假设我有以下类型:

type A = {
    role: 'admin',
    force?: boolean
}

type B = {
    role: 'regular',
}

type Or = A | B;

我希望能够做到:

const or = {
    role: 'regular'
} as Or;

const {role, force} = or;

而不会出现错误:

Property 'force' does not exist on type 'Or'.(2339)

不使用触摸AB
我试着去做:

type Merge<Obj> = {
    [k in keyof Obj]: Obj[k]
}

const {role, force} = or as Merge<Or>;

但同样的错误
打字机游戏场

slhcrj9b

slhcrj9b1#

TypeScript对象类型不是 sealedexact(如microsoft/TypeScript#12936中所要求的)。仅仅因为一个类型没有提到一个属性,它并没有 * 禁止 * 该属性。所以从技术上讲,类型{role: "regular"}的值可能有一个名为force的属性,该属性的类型完全未知:

type A = { role: 'admin', force?: boolean };
type B = { role: 'regular' };
type Or = A | B;

const hmm = { role: 'regular', force: 9000 } as const;
const oops: Or = hmm; // <-- accepted

按照这种逻辑,你要做的事情是不安全的,唯一一个将Or类型的值分解成碎片的 * 安全 * 方法是使force属性成为unknown类型。
但是从现在开始,让我们假设这个问题不会出现,并且我们Assert该值只具有编译器已知的属性,并且该类型中没有提到的任何属性都将是undefined类型。
那么,我们如何“修复”像Or这样的联合类型,使其成为一个显式知道其他成员的所有键的版本,以便编译器允许您使用这些键中的任何键对它进行索引?
这里有一个可能的方法:

type FixUnion<T, K extends PropertyKey = T extends unknown ? keyof T : never> =
    T extends unknown ? (
        T & { [P in Exclude<K, keyof T>]?: never } extends infer O ? {
            [P in keyof O]: O[P]
        } : never
    ) : never

首先让我们确认它在Or上是否工作:

type FixedOr = FixUnion<Or>;
/* type FixedOr  = {
    role: 'admin';
    force?: boolean | undefined;
} | {
    role: 'regular';
    force?: undefined;
} */

这看起来是正确的。两个联合体成员都有一个role和一个force属性。第一个成员与A相同,而第二个成员是B,它带有一个undefined类型的可选属性。因此,现在您可以对它进行反结构:

const { role, force } = or as FixUnion<Or>;
// const role: "admin" | "regular"
// const force: boolean | undefined

那么它是如何工作的呢?首先让我们看看K类型参数。我实际上只是使用一个默认值来计算T联合体中每个成员的所有键。类型T extends unknown ? keyof T : never是一个分布式条件类型,它将T分解为它的联合体成员,获取它们各自的键,然后将它们重新组合成一个新的并集,我有时将此操作称为AllKeys<T>,如Is it possible to get the keys from a union of objects?所示,对于OrK将为"role" | "force"
所以现在K是所有的键,FixUnion<T>的主体是另一个分布式条件类型;对于T联合体的每个成员,我们将其与{ [P in Exclude<K, keyof T>]?: never }求交集,这是一个具有所有可选键的mapped type(因此是?),它们存在于K中,但不在当前联合成员中(使用Exclude<T, U>实用程序类型)。并且属性值是never,不可能的类型。对于Or,第一个成员将看起来像A & {},第二个成员将看起来像B & { force?: never }。注意,可选属性自动在其域中获得undefined,所以它和B & { force?: undefined }是一样的,关键是第二个联合成员中的force属性不是丢失就是undefined
最后,为了使事物更美观,我使用了一种技术,将像A & {}这样的交叉点变成像{role: "admin", force?: boolean}这样的普通对象,如How can I see the full expanded contract of a Typescript type?中所述;... extends infer O ? {[P in keyof O]: O[P]} : never}就是这么做的,所以第一个成员变成了{role: "admin", force?: boolean},第二个变成了{role: "regular", force?: undefined}
让我们在另一个联合体上测试一下,看看它是如何工作的:

type Foo = FixUnion<{ a: 0 } | { b: 1 } | { a: 2 } | { c: 3 }>;
/* type Foo = {
    a: 0;
    b?: undefined;
    c?: undefined;
} | {
    b: 1;
    a?: undefined;
    c?: undefined;
} | {
    a: 2;
    b?: undefined;
    c?: undefined;
} | {
    c: 3;
    a?: undefined;
    b?: undefined;
} */

所有四个联合成员都具有所有三个键,但至少有两个键是undefined类型的可选键。
Playground代码链接

eoigrqb6

eoigrqb62#

不能访问只有联合类型的某些成员才具有的联合类型的属性。
你会得到同样的错误,同样的原因是这个简化的代码:

type A = {
    role: 'admin',
    force?: boolean
}

type B = {
    role: 'regular',
}

const obj: A | B =
    Math.random() > 0.5 ?
        { role: 'admin', force: true } :
        { role: 'regular' }

obj.force // type error

如果你想要那个属性,那么你必须首先证明它存在,这就把你的并集缩小到了那个属性的值。

if ('force' in obj) obj.force // fine

见Playground
你可以把同样的东西添加到你的操场上,以使类型检查通过。

const mergedOr = or as Merge<Or>;
const force = 'force' in or && or.force; // fine

见Playground
另外值得一提的是,这种类型:

type Merge<Obj> = {
    [k in keyof Obj]: Obj[k]
}

没有什么用,它把一个对象类型的键Map到这些键的值类型,它和输入类型完全一样,所以不管你认为它应该做什么,它没有做。

kxxlusnw

kxxlusnw3#

使用Pick实用程序来实现这一点,下面是我的解决方案,您可能会认为有帮助:

type A = {
    role: 'admin',
    force?: boolean
}

type B = {
    role: 'regular',
}
type Or = Pick<A & B, 'force' | 'role'>;
const or = {
    role: 'regular'
} as Or;

const {role, force} = or;
hmae6n7t

hmae6n7t4#

乡巴佬黑客:
const {role, force} = or as any;
但说真的,你不能。 typescript 正在帮助你解决这个问题。如果你这样做了,它会起作用:

const or: Or =
    role: 'admin'
};

const {role, force} = or;

这样,编译器将它与类型A匹配,类型A至少有机会具有force,因此这里的force是类型boolean | undefined

相关问题