Generics 具有混合成员类型的泛型TypeScript接口
对于一些HTML表单,我想配置输入并处理它们的值。“我的类型”具有以下结构,您可以将其复制到TypeScript Playerd以查看其运行情况:Generics 具有混合成员类型的泛型TypeScript接口,generics,typescript,interface,union,intersection,Generics,Typescript,Interface,Union,Intersection,对于一些HTML表单,我想配置输入并处理它们的值。“我的类型”具有以下结构,您可以将其复制到TypeScript Playerd以查看其运行情况: interface InputConfig { readonly inputType: "text" | "password" | "list" readonly attributes?: ReadonlyArray<string> readonly cssClasses?: ReadonlyArray<st
interface InputConfig {
readonly inputType: "text" | "password" | "list"
readonly attributes?: ReadonlyArray<string>
readonly cssClasses?: ReadonlyArray<string>
}
interface Inputs<T extends InputConfig | string | string[]> {
readonly username: (T & InputConfig) | (T & string)
readonly password: (T & InputConfig) | (T & string)
readonly passwordConfirmation: (T & InputConfig) | (T & string)
readonly firstname: (T & InputConfig) | (T & string)
readonly lastname: (T & InputConfig) | (T & string)
readonly hobbies: (T & InputConfig) | (T & string[])
}
type InputConfigs = Inputs<InputConfig> // case 1 (see below)
type InputValues = Inputs<string | string[]> // case 2 (see below)
const configs: InputConfigs = {
username: {
inputType: "text"
},
password: {
inputType: "password"
},
passwordConfirmation: {
inputType: "password"
},
firstname: {
inputType: "text"
},
lastname: {
inputType: "text"
},
hobbies: {
inputType: "list"
}
}
const values: InputValues = {
username: "testuser1",
password: "test1",
passwordConfirmation: "test1",
firstname: "Tester",
lastname: "1",
hobbies: ["a", "b", "c"]
}
const username: string = values.username
我希望它能起作用,因为我的理解是:
案例1:T是InputConfig,因此用户名的类型为InputConfig,因为:
T&InputConfig=InputConfig&InputConfig=InputConfig
T&string=InputConfig&string=nothing
案例2:T是string | string[],因此用户名的类型为string,因为:
T&InputConfig=string | string[]&InputConfig=nothing
T&string=string | string[]&string=string
当您有一个union Foo | Bar并希望将其用作Foo时,您有两个选项:
使用铅字防护装置
在您的情况下,您只希望它是一个字符串更改
到
使用类型断言
注意一点。首先,让我们探讨一下您遇到的类型错误。如果我在编辑器中将鼠标悬停在TypeScript上,询问values.username的类型,我会得到
values.username : string
| (string & InputConfig)
| (string[] & InputConfig)
| (string[] & string)
TypeScript通过将Inputs的T参数实例化为string | string[]并将用户名的类型转换为析取范式来实现此类型:
(T & InputConfig) | (T & string)
// set T ~ (string | string[])
((string | string[]) & InputConfig) | ((string | string[]) & string)
// distribute & over |
((string & InputConfig) | (string[] & InputConfig)) | ((string & string) | (string[] & string))
// reduce (string & string) ~ string, permute union operands
string | (string & InputConfig) | (string[] & InputConfig) | (string[] & string)
此类型的值是否可分配给字符串?不username可以是字符串[]&InputConfig,因此const x:string=values.username类型错误
对比一下你的另一种类型
configs.username : InputConfig | (InputConfig & string)
此类型可分配给InputConfig
这通常是类型系统的一个缺点:它们倾向于拒绝在运行时仍能工作的程序。您可能知道values.username将始终是一个字符串,但您还没有证明它符合类型系统的要求,它只是一个愚蠢的机械形式系统。我通常认为,类型良好的程序往往更容易理解,而且随着时间的推移,随着代码的更改,更容易继续工作。尽管如此,在像TypeScript这样的高级类型系统中工作是一项后天习得的技能,而且很容易陷入死胡同,试图让类型系统实现您想要的功能
我们如何解决这个问题?我有点不清楚您试图实现什么,但我将您的问题解释为如何使用各种不同类型的记录的字段名?这似乎是我的工作
映射类型是TypeScript类型系统的高级功能,但其思想很简单:首先将字段名声明为文本类型的联合,然后描述从这些字段名派生的各种类型。具体地说:
type InputFields = "username"
| "password"
| "passwordConfirmation"
| "firstname"
| "lastname"
| "hobbies"
现在,InputConfigs是一个包含所有这些InputFields的记录,每个InputFields都被键入为只读InputConfig。TypeScript库为此提供了一些功能:
type InputConfigs = Readonly<Record<InputFields, InputConfig>>
当应用于所讨论的类型时,我们得到:
type InputConfigs = Readonly<Record<InputFields, InputConfig>>
// expand definition of Record
type InputConfigs = Readonly<{ [P in InputFields]: InputConfig; }>
// expand definition of Readonly
type InputConfigs = {
readonly [Q in keyof { [P in InputFields]: InputConfig }]: { [P in InputFields]: InputConfig }[Q];
}
// inline keyof and subscript operators
type InputConfigs = {
readonly [Q in InputFields]: InputConfig;
}
// expand InputFields indexer
type InputConfigs = {
readonly username: InputConfig;
readonly password: InputConfig;
readonly passwordConfirmation: InputConfig;
readonly firstname: InputConfig;
readonly lastname: InputConfig;
readonly hobbies: InputConfig;
}
现在,您的代码类型将在不修改的情况下进行检查
常量配置:输入配置={
用户名:{
输入类型:文本
},
密码:{
输入类型:密码
},
密码确认:{
输入类型:密码
},
名字:{
输入类型:文本
},
姓氏:{
输入类型:文本
},
爱好:{
输入类型:列表
}
}
常量值:输入值={
用户名:testuser1,
密码:test1,
密码确认:test1,
名字:测试员,
姓:1,,
爱好:[a、b、c]
}
让usernameConfig:InputConfig=configs.username//好!
让usernameValue:string=values.username//太好了!
让hobbiesValue:string[]=values.cabbies//更好!我的类型安全表单/字段状态解决方案:适用于任何地方mobx适用于react/angular/inferno/vue。感谢您的输入。我来看看。MobX在我的观察名单上,因为它是一种很好的状态管理方法。但就目前而言,我更喜欢这个问题所涉及的只有TypeScript的解决方案。类型InputValues本身应该可以做到这一点,而不必多次写入成员名称。我必须说,我觉得这很有趣。这真是一个优雅的解决方案。非常感谢你的帮助和出色的回答。即使我不希望像本案那样有更好的回答,我通常会等到悬赏期结束。
configs.username : InputConfig | (InputConfig & string)
type InputFields = "username"
| "password"
| "passwordConfirmation"
| "firstname"
| "lastname"
| "hobbies"
type InputConfigs = Readonly<Record<InputFields, InputConfig>>
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
type Record<K extends string, T> = {
[P in K]: T;
}
type InputConfigs = Readonly<Record<InputFields, InputConfig>>
// expand definition of Record
type InputConfigs = Readonly<{ [P in InputFields]: InputConfig; }>
// expand definition of Readonly
type InputConfigs = {
readonly [Q in keyof { [P in InputFields]: InputConfig }]: { [P in InputFields]: InputConfig }[Q];
}
// inline keyof and subscript operators
type InputConfigs = {
readonly [Q in InputFields]: InputConfig;
}
// expand InputFields indexer
type InputConfigs = {
readonly username: InputConfig;
readonly password: InputConfig;
readonly passwordConfirmation: InputConfig;
readonly firstname: InputConfig;
readonly lastname: InputConfig;
readonly hobbies: InputConfig;
}
type InputValues = Readonly<{
username: string
password: string
passwordConfirmation: string
firstname: string
lastname: string
hobbies: string[]
}>
type InputFields = keyof InputValues
type InputConfigs = Readonly<Record<InputFields, InputConfig>>