我有一个类似于此的对象类型:
type Fields = {
countryCode: string;
currency: string;
otherFields: string;
};
我也有一个类似这样的只读数组:
// Type: readonly ["countryCode", "currency", "otherFields"]
const allowedFields = ["countryCode", "currency", "otherFields"] as const;
我希望能够根据 Fields
对象类型为此数组声明指定一个接口(interface),以便对其进行的任何更改也需要更改数组。像这样:
// How to create 'SomeType'?
const allowedFields: SomeType = ["countryCode"] as const; // Should throw error because there are missing fields
const allowedFields: SomeType = ["extraField"] as const; // Should throw error because "extraField" is not in the object type 'Fields'
最佳答案
type Fields = {
countryCode: string;
currency: string;
otherFields: string;
};
// credits goes to https://twitter.com/WrocTypeScript/status/1306296710407352321
type TupleUnion<U extends string, R extends any[] = []> = {
[S in U]: Exclude<U, S> extends never ? [...R, S] : TupleUnion<Exclude<U, S>, [...R, S]>;
}[U];
type AllowedFields = TupleUnion<keyof Fields>;
const allowedFields: AllowedFields = ["countryCode", "currency", "otherFields"];
// How to create 'SomeType'?
const foo: AllowedFields = ["countryCode"]; // Should throw error because there are missing fields
const bar: AllowedFields = ["extraField"]; // Should throw error because "extraField" is not in the object type 'Fields'
您需要创建所有允许的 Prop 的排列。为什么排列?因为字典的键是无序的。
解释
让我们摆脱递归调用和条件类型:
{
type TupleUnion<U extends string, R extends any[] = []> = {
[S in U]: [...R, S]
}
type AllowedFields = TupleUnion<keyof Fields>;
type AllowedFields = {
countryCode: ["countryCode"];
currency: ["currency"];
otherFields: ["otherFields"];
}
}
我们创建了一个对象,其中每个值都是一个包含 key
的元组.
为了完成任务,每个值应该包含不同顺序的每个键。
像那样:
type AllowedFields = {
countryCode: ["countryCode", 'currency', 'otherFields'];
currency: ["currency", 'countryCode', 'otherFields'];
otherFields: ["otherFields", 'countryCode', 'currency'];
}
因此,为了添加另外两个 Prop ,我们需要调用 TupleUnion
递归地,但没有元组中已经存在的元素。这意味着,我们的第二个调用应该这样做:
type AllowedFields = {
countryCode: ["countryCode", Exclude<Fields, 'countryCode'>];
currency: ["currency", Exclude<Fields, 'currency'>];
otherFields: ["otherFields", Exclude<Fields, 'otherFields'>];
}
为了实现,我们需要这样做:TupleUnion<Exclude<U, S>, [...R, S]>;
.如果我这样写可能会更易读:
type TupleUnion<FieldKeys extends string, Result extends any[] = []> = {
[Key in FieldKeys]: TupleUnion<Exclude<FieldKeys, Key>, [...Result, Key]>;
}
但是接下来我们将生成深度嵌套的数据结构:
type AllowedFields = TupleUnion<keyof Fields>['countryCode']['currency']['otherFields']
我们不应该调用 TupleUnion
递归如果 Exclude<U, S>
,或者换句话说 Exclude<FieldKeys, Key>
返回 never
.我们需要检查是否 Key
是最后一个属性。为此,我们可以检查是否 Exclude<U, S> extends never
.如果是 never
- 没有更多的 key ,我们可以返回 [...R,S]
.
我希望这段代码:
{
type TupleUnion<FieldKeys extends string, Result extends any[] = []> = {
[Key in FieldKeys]: Exclude<FieldKeys, Key> extends never ? [...Result, Key] : TupleUnion<Exclude<FieldKeys, Key>, [...Result, Key]>;
}
type AllowedFields = TupleUnion<keyof Fields>
}
更清晰。但是,我们仍然有一个带有值而不是元组的对象。对象中的每个值都是所需类型的元组。为了获得所有值的并集,我们只需要使用方括号表示法和所有键的并集。像那样:type A = {age:1,name:2}['age'|'name'] // 1|2
.
最终代码:
type TupleUnion<FieldKeys extends string, Result extends any[] = []> = {
[Key in FieldKeys]: Exclude<FieldKeys, Key> extends never ? [...Result, Key] : TupleUnion<Exclude<FieldKeys, Key>, [...Result, Key]>;
}[FieldKeys] // added suqare bracket notation with union of all keys
关于typescript - 从对象类型创建常量数组类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69676439/