Typescript 扩展运算符和可选字段。如何推断合适的类型
假设您有一个具有不可为null的必填字段的对象:Typescript 扩展运算符和可选字段。如何推断合适的类型,typescript,spread-syntax,Typescript,Spread Syntax,假设您有一个具有不可为null的必填字段的对象: interface MyTypeRequired { value: number; } 您希望使用另一个对象的字段更新它,并使用可选字段: interface MyTypeOptional { value?: number; } 因此,您可以继续创建一个函数: function createObject(a: MyTypeRequired, b: MyTypeOptional) { return { ...a, .
interface MyTypeRequired {
value: number;
}
您希望使用另一个对象的字段更新它,并使用可选字段:
interface MyTypeOptional {
value?: number;
}
因此,您可以继续创建一个函数:
function createObject(a: MyTypeRequired, b: MyTypeOptional) {
return { ...a, ...b };
}
type Spread<L, R> = Pick<L, Exclude<keyof L, keyof R>> & R;
function spread<T, U>(a: T, b: U): Spread<T, U> {
return { ...a, ...b };
}
const t1_: { a: number; b: string }
const t2_: { a?: number; b: string }
const u1_: { a: number; c: boolean }
const u2_: { a?: number; c: boolean }
const t1u2 = spread(t1_, u2_); // { b: string; a?: number | undefined; c: boolean; }
const t2u1 = spread(t2_, u1_); // { b: string; a: number; c: boolean; }
此函数的推断返回类型是什么
const a = createObject({ value: 1 }, { value: undefined });
实验表明,它将符合MyTypeRequired接口,即使第二个排列有一个可选字段
如果我们更改顺序,则推断的类型不会更改,即使运行时类型会有所不同
function createObject(a: MyTypeRequired, b: MyTypeOptional) {
return { ...b, ...a };
}
为什么TypeScript有这样的行为以及如何解决这个问题?可以通过类型级别的函数来解决。考虑:
interface MyTypeRequired {
a: number;
value: number;
}
interface MyTypeOptional {
b: string;
value?: number;
}
type MergedWithOptional<T1, T2> = {
[K in keyof T1]: K extends keyof T2 ? T2[K] extends undefined ? undefined : T2[K] : T1[K]
} & {
[K in Exclude<keyof T2, keyof T1>]: T2[K]
}
function createObject(a: MyTypeRequired, b: MyTypeOptional): MergedWithOptional<MyTypeRequired, MyTypeOptional> {
return { ...b, ...a };
}
接口MyTypeRequired{
a:数字;
值:数字;
}
接口MyTypeOptional{
b:弦;
值?:数字;
}
类型MergedWithOptional={
[K in keyof T1]:K扩展T2的keyof?T2[K]扩展未定义?未定义:T2[K]:T1[K]
} & {
[K in Exclude]:T2[K]
}
函数createObject(a:MyTypeRequired,b:MyTypeOptional):MergedWithOptional{
返回{…b,…a};
}
为了检查行为,我添加了其他字段。我所做的是,当第二个对象有可选字段(可能<代码>未定义< /代码>)时,考虑将此添加到联合,因此结果将是<代码> t1[k]未定义的< /代码>。第二部分只是合并T2
中但不在T1
中的所有其他字段
更多详情:
如果第二个对象具有此键,并且其值可以未定义,则将未定义的追加到值类型,并将其追加而不替换,因为这是K扩展T2的键?T2[K]扩展未定义?未定义:T2[K]:T1[K]
-仅使用不在第一个对象中的键排除
interface MyTypeRequired {
a: number;
value: number;
}
interface MyTypeOptional {
b: string;
value?: number;
}
type MergedWithOptional<T1, T2> = {
[K in keyof T1]: K extends keyof T2 ? T2[K] extends undefined ? undefined : T2[K] : T1[K]
} & {
[K in Exclude<keyof T2, keyof T1>]: T2[K]
}
function createObject(a: MyTypeRequired, b: MyTypeOptional): MergedWithOptional<MyTypeRequired, MyTypeOptional> {
return { ...b, ...a };
}
接口MyTypeRequired{
a:数字;
值:数字;
}
接口MyTypeOptional{
b:弦;
值?:数字;
}
类型MergedWithOptional={
[K in keyof T1]:K扩展T2的keyof?T2[K]扩展未定义?未定义:T2[K]:T1[K]
} & {
[K in Exclude]:T2[K]
}
函数createObject(a:MyTypeRequired,b:MyTypeOptional):MergedWithOptional{
返回{…b,…a};
}
为了检查行为,我添加了其他字段。我所做的是,当第二个对象有可选字段(可能<代码>未定义< /代码>)时,考虑将此添加到联合,因此结果将是<代码> t1[k]未定义的< /代码>。第二部分只是合并T2
中但不在T1
中的所有其他字段
更多详情:
如果第二个对象具有此键,并且其值可以未定义,则将未定义的追加到值类型,并将其追加而不替换,因为这是K扩展T2的键?T2[K]扩展未定义?未定义:T2[K]:T1[K]
-仅使用不在第一个对象中的键排除
- 警告!这是我对所发生事情的解释(理论)。还没有确定
这个问题似乎是处理可选字段的预期行为 定义此类型时:
interface MyTypeOptional {
value?: number;
}
TypeScript期望值的类型为number | never
所以当你传播它的时候
const withOptional: MyTypeOptional = {};
const spread = { ...withOptional };
排列对象的类型应为{value:never | number}
或{value?:number}
不幸的是,由于可选字段与未定义类型的用法不明确,脚本推断扩展对象如下:
value?:数字|未定义
因此,扩展时,可选字段类型被转换为number | undefined
不合理的是,当我们传播非可选类型时,它似乎覆盖了可选类型:
interface MyTypeNonOptional {
value: number;
}
const nonOptional = { value: 1 };
const spread2 = { ...nonOptional, ...withOptional }
看起来像只虫子?但不是,这里TypeScript不会将可选字段强制转换为number | undefined
。在这里,它将其转换为number | never
我们可以通过将可选更改为显式|未定义:
interface MyTypeOptional {
value: number | undefined;
}
const u2 = { a: undefined }
const spread4 = { ...t1, ...u2 } // { a: undefined; }
在这里,以下排列:
const withOptional: MyTypeOptional = {} as MyTypeOptional;
const nonOptional = { value: 1 };
const spread2 = { ...nonOptional, ...withOptional }
将被推断为{value:number |未定义;}
当我们将其更改为可选或未定义时:
interface MyTypeOptional {
value?: number | undefined;
}
它再次被破坏并推断为{value:number}
那么,什么是解决办法
我想说的是,在TypeScript团队修复之前不要使用可选字段
这有几个含义:
- 如果需要在未定义的情况下省略对象的键,请使用
省略未定义(对象)
- 如果需要使用Partial,请通过创建工厂来限制其使用:
createPartial(o:Partial)=>Undefinable
。因此,createPartial({})
的结果将被推断为{value:undefined}
或undefineable
- 如果需要将字段的值设置为
unset
,请使用null
关于可选字段的限制存在一个公开问题:
警告!这是我对所发生事情的解释(理论)。还没有确定
这个问题似乎是处理可选字段的预期行为
定义此类型时:
interface MyTypeOptional {
value?: number;
}
TypeScript期望值的类型为number | never
所以当你传播它的时候
const withOptional: MyTypeOptional = {};
const spread = { ...withOptional };
排列对象的类型应为{value:never | number}
或{value?:number}
不幸的是,由于可选字段与未定义类型的用法不明确,脚本推断扩展对象如下:
value?:数字|未定义
因此,扩展时,可选字段类型被转换为number | undefined
不合理的是,当我们传播非可选类型时,它似乎覆盖了可选类型:
interface MyTypeNonOptional {
value: number;
}
const nonOptional = { value: 1 };
const spread2 = { ...nonOptional, ...withOptional }
看起来像只虫子?但不,这里TypeScript不播放op