我有这样的数据:
const data = [
{ id: 1, a: "foo" },
{ id: 2 },
] as const;
我可以";验证";它与现有的类型:
type Row = {
readonly id: number;
readonly a?: string;
};
// This will explode if data has { b: "bar" }
const dataValidated: Readonly<Row[]> = data;
但我得到了";类型上不存在属性"a"当我尝试访问它时:
data.map((x) => x.a);
// ^ Error here
如果我使用dataValidated
,我将失去as const
:的类型窄化
dataValidated.map((x) => x.id);
// ^^
// number, but I want 1 | 2
dataValidated.map((x) => x.a);
// ^
// string | undefined, but I want "foo" | undefined
我尝试过一个简单的交集,它对id
有帮助,但对a
:没有帮助
const dataIntersected: Readonly<(Row & typeof data[number])[]> = data;
dataIntersected.map((x) => x.id);
// ^^
// Good, it's 1 | 2 now
dataIntersected.map((x) => x.a);
// ^
// Still string | undefined
如何访问所有字段而不进行类型扩展或更改数据(例如,在第二项中添加冗余a: undefined
(?
相关:打字:联合类型到可选值的深度交集(难度:95+级(
映射类型!
const dataSolved: {
[K in keyof Row]: Extract<
typeof data[number],
Pick<Row, K>
>[K]
} = data;
dataSolved.map((x) => x.id);
// ^^
// 1 | 2
dataSolved.map((x) => x.a);
// ^
// "foo" | undefined
{ [K in keyof Row]: ... }
逐个遍历Row
的所有字段;指定";至CCD_ 8。
typeof data[number]
只是所有文字行类型的并集,在这种情况下与data
相同。
Pick<Row, K>
丢弃Row
中除K
之外的所有字段,因此对于K = 'a'
,结果为{ readonly a?: string }
。
Extract
从上面提到的联合中丢弃了与Pick<Row, K>
不匹配的所有类型,所以对于K = 'a'
,结果是{ id: 1, a: "foo" }
。
Extract
之后的[K]
创建了字段K
的并集,该字段在Extract
返回的并集中是公共的,因此对于K = 'a'
,它是"foo"
。
CCD_ 26和CCD_;选择性;保持不变,默认情况下贴图不会接触到它们。
为了简洁起见,这是一个基本示例。该解决方案更多地涉及嵌套结构,但对于递归条件类型和分布性仍然是可能的:
type Primitive = string | number | boolean | null | undefined;
type Combine<T extends U, U> = T extends Readonly<Primitive | Primitive[]>
? T
: T extends ReadonlyArray<infer I>
? Readonly<Combine<I, Extract<U, Readonly<any[]>>[number]>[]>
: U extends Readonly<Primitive | any[]>
? never
: string extends keyof U
? { [K in keyof T & string]: Combine<T[K], U[K]> }
: number extends keyof U
? { [K in keyof T & number]: Combine<T[K], U[K]> }
: symbol extends keyof U
? { [K in keyof T & symbol]: Combine<T[K], U[K]> }
: { [K in keyof U]: Combine<T[K], U[K]> };
const combined: Combine<typeof data, Readonly<Row[]>> = data;
与这个问题有一定关系的是,也可以使所有部分存在的字段都是可选的,而不使用";模式";的数据(不需要Row
(,具有密钥重映射:
type AllKeys<T> = T extends any ? keyof T : never;
type WithKey<T, K extends keyof T> = Extract<T, { [_ in K]: any }>[K];
type OptionalizeHelper<T> = [T] extends [Readonly<Primitive | Primitive[]>]
? T
: [T] extends [ReadonlyArray<infer I>]
? Readonly<Optionalize<I>[]>
: {
[K in AllKeys<T> as K extends keyof T ? K : never]: Optionalize<
WithKey<T, K>
>;
} & {
[K in AllKeys<T> as K extends keyof T ? never : K]?: Optionalize<
WithKey<T, K>
>;
};
type Optionalize<T> =
| OptionalizeHelper<Extract<T, Readonly<Primitive | Primitive[]>>>
| OptionalizeHelper<Extract<T, Readonly<any[]>>>
| OptionalizeHelper<Exclude<T, Readonly<Primitive | any[]>>>;
const optionalized: Optionalize<typeof data> = data;
我创建了一个游乐场,通过类型测试演示了Combine
和Optionalize
的用法。
我后来发现这个答案中的Widen
类型非常接近Optionalize
,但不能很好地处理元组和数组。