Typescript:通过添加具有特定命名的新字段来动态扩展接口
Typescript: Extend interface dynamically by adding new fields with certain naming
我有这样的界面:
interface ProjectCostData {
purchasePrice: number;
propertyValue: number;
recentlyDamaged: boolean;
}
现在我想在上面的接口的基础上动态创建一个这样的接口:
interface ProjectCostDataWithSetters {
purchasePrice: number;
setPurchasePrice: Dispatch<SetStateAction<number>>;
propertyValue: number;
setPropertyValue: Dispatch<SetStateAction<number>>;
recentlyDamaged: boolean;
setRecentlyDamage: Dispatch<SetStateAction<boolean>>;
}
--> 映射到第一个接口,并为每个条目 A 添加一个新的条目 B,其键为“set${entryKey}”和一个可以从条目 A 派生的类型。
这在 TS 中可行吗?
背景:我有一个基于 OpenAPI 的后端,它为我提供 API 结构作为 .yaml
文件。我通过生成器传递该文件并接收接口和函数以与 API 对话。在前端,我使用反应上下文来构建全局状态。我想根据生成器的输出构建我的全局反应上下文。
在 typescript 4.0 或更早版本中,这是不可能的。字符串,包括 属性 名称,或者完全称为常量(即 "foo"
),一组有限可能性的常量之一(即 "foo" | "bar"
),或者只是具有任何内容的任何字符串(string
)。所以你不能根据其他 属性 名称构造新的 属性 名称。
但是,Typescript 4.1(目前是测试版)有一些功能可能对 template literal types
有帮助
interface ProjectCostData {
purchasePrice: number;
propertyValue: number;
recentlyDamaged: boolean;
}
// Create a new setter property for each key of an interface
type WithSetters<T extends { [k: string]: any }> = T & {
[K in keyof T & string as `set_${K}`]: (newValue: T[K]) => void
}
// Testing
declare const obj: WithSetters<ProjectCostData>
obj.purchasePrice // number
obj.set_purchasePrice(123) // (newValue: number) => void
那个:
[K in keyof T & string as `set_${K}`]
有点古怪。 This PR explains that better than I could.
您甚至可以处理大小写更改:
interface Capitals { a: 'A', b: 'B', c: 'C' /* ... */, p: 'P' }
type Capitalize<T extends string> =
T extends `${infer First}${infer Rest}`
? First extends keyof Capitals
? `${Capitals[First]}${Rest}`
: T
: T
type SetterName<T extends string> = `set${Capitalize<T>}`
type SetPropName = SetterName<'propName'> // type: "setPropName"
将所有这些放在一起有点棘手,尤其是在测试版上。但我认为,从理论上讲,所有的部分都是为了实现这种类型转换而存在的。
我有这样的界面:
interface ProjectCostData {
purchasePrice: number;
propertyValue: number;
recentlyDamaged: boolean;
}
现在我想在上面的接口的基础上动态创建一个这样的接口:
interface ProjectCostDataWithSetters {
purchasePrice: number;
setPurchasePrice: Dispatch<SetStateAction<number>>;
propertyValue: number;
setPropertyValue: Dispatch<SetStateAction<number>>;
recentlyDamaged: boolean;
setRecentlyDamage: Dispatch<SetStateAction<boolean>>;
}
--> 映射到第一个接口,并为每个条目 A 添加一个新的条目 B,其键为“set${entryKey}”和一个可以从条目 A 派生的类型。
这在 TS 中可行吗?
背景:我有一个基于 OpenAPI 的后端,它为我提供 API 结构作为 .yaml
文件。我通过生成器传递该文件并接收接口和函数以与 API 对话。在前端,我使用反应上下文来构建全局状态。我想根据生成器的输出构建我的全局反应上下文。
在 typescript 4.0 或更早版本中,这是不可能的。字符串,包括 属性 名称,或者完全称为常量(即 "foo"
),一组有限可能性的常量之一(即 "foo" | "bar"
),或者只是具有任何内容的任何字符串(string
)。所以你不能根据其他 属性 名称构造新的 属性 名称。
但是,Typescript 4.1(目前是测试版)有一些功能可能对 template literal types
有帮助interface ProjectCostData {
purchasePrice: number;
propertyValue: number;
recentlyDamaged: boolean;
}
// Create a new setter property for each key of an interface
type WithSetters<T extends { [k: string]: any }> = T & {
[K in keyof T & string as `set_${K}`]: (newValue: T[K]) => void
}
// Testing
declare const obj: WithSetters<ProjectCostData>
obj.purchasePrice // number
obj.set_purchasePrice(123) // (newValue: number) => void
那个:
[K in keyof T & string as `set_${K}`]
有点古怪。 This PR explains that better than I could.
您甚至可以处理大小写更改:
interface Capitals { a: 'A', b: 'B', c: 'C' /* ... */, p: 'P' }
type Capitalize<T extends string> =
T extends `${infer First}${infer Rest}`
? First extends keyof Capitals
? `${Capitals[First]}${Rest}`
: T
: T
type SetterName<T extends string> = `set${Capitalize<T>}`
type SetPropName = SetterName<'propName'> // type: "setPropName"
将所有这些放在一起有点棘手,尤其是在测试版上。但我认为,从理论上讲,所有的部分都是为了实现这种类型转换而存在的。