我有一个这样的对象:
const state = {
isFormOK: true,
fieldA: {
value: 'abc';
status: 'ok' | 'normal' | 'error';
errorText: '';
}
}
可能有许多字段具有与上述fieldA
属性相同的模式,并且在代码中,我将实际使用index属性来访问它们,因为它们是随机的。所以我声明了这样的类型:
export interface FieldState {
value: string;
status: 'ok' | 'normal' | 'error';
errorText: string;
}
export interface ComponentState {
isFormOK: boolean;
[fieldName: string]: FieldState
}
如果您只是将interface
与type
交换,那么它适用于flow.js
。虽然不能在Typescript中做到这一点,因为正如文档所说:
虽然字符串索引签名是描述“字典”模式的有效方式,但它们还强制所有属性都与其返回类型匹配。这是因为字符串索引声明obj.property也可用作obj [“property”]。
错误是:
isFormOK
类型的属性boolean
无法分配给字符串索引类型FieldState
我认为没关系,因为当我使用索引访问状态时,我可能会转到索引等于isFormOK
的情况,但我仍然希望像FieldState
一样使用它。我知道。但我怎么能解决它并输入检查这个对象?
我尝试将[fieldName: string]: FieldState
修改为[fieldName: string]: FieldState | boolean
。然后传递此接口声明,但稍后在代码中,state[randomFieldName].value
将出现如下错误:Property value doesn't exist on type boolean | FieldState
。
尝试将[fieldName: string]: FieldState
更改为[fieldName: string]: {}
,然后在每次使用时将其投放到FieldState
。有用。但是state
的类型并没有传达这个对象的真实模式的消息。
那么如何在不使用state
或isFormOK
的情况下键入fieldA
{}
对象,包括any
和Task.Run
?
答案 0 :(得分:0)
它适用于类型别名和交集类型:
type FieldState = {
value: string;
status: 'ok' | 'normal' | 'error';
errorText: string;
}
type ComponentState = { isFormOK: boolean; } & { [fieldName: string]: FieldState };
declare const state: ComponentState;
state.isFormOK = true // (property) isFormOK: boolean
state.foobar.value // (property) value: string
state["foobar"].status // (property) status: "ok" | "normal" | "error"
正如 @jcalz 所指出的,将对象文字分配给此类似乎存在问题,具体而言isFormOK
应该具有类型boolean & FieldState
(即使属性访问state.isFormOK
解析为boolean
},这是一种不可用的类型,当您尝试分配给它时会导致错误:
const state: ComponentState = { isFormOK: true, fieldA: { value: "foo", status: "ok", errorText: "err" } }
Property 'isFormOK' is incompatible with index signature. Type 'true' is not assignable to type 'FieldState'.
如果您可以选择isFormOK
,或者从Partial<ComponentState>
开始,则可以在之后分配isFormOk
:
type ComponentState = { isFormOK?: boolean } & { [fieldName: string]: FieldState };
const state: ComponentState = { fieldA: { value: "foo", status: "ok", errorText: "err" } }
state.isFormOK = true;
关于此的问题目前在Github上开放:https://github.com/Microsoft/TypeScript/issues/17867
根据上面链接的问题,TS认为让一个索引器与所有属性不匹配是个坏主意,即使Flow允许它,因为这样的错误:
let key: string = "isFormOK"
state[key] // FieldState because key is string, but at runtime its a boolean
如果您知道预期的字段名称,则一种可能的解决方法是使用mapped types而不是索引类型:
type ComponentState<F extends string> = { isFormOK: boolean; } & { [P in F]: FieldState };
const state: ComponentState<"fieldA"> = {
isFormOK: true,
fieldA: {
value: "foo",
status: "ok",
errorText: "err"
}
}
如果您提前知道字段名称,这可能会更好,因为它会防止尝试引用不存在的字段(如上面的state.fieldZ
)。
如果您在其他地方使用了键,则可以从值派生键,例如:
function submitFields<K extends string>(fields: { [P in K]: any }): ComponentState<K> {
// ...
}
const state = submitFields({ fieldA: "foo", fieldB: "bar" })
state.isFormOK
state.fieldA.status
state.fieldB.status
如果不提前知道字段名称(例如通用表单构建器工具),那么这将无法让您一路走来。 (编辑 jcalz似乎是一个可以使用动态字段名称的泛型类型的一步,检查出来!)
答案 1 :(得分:0)
TypeScript目前没有办法表达&#34;所有可能的密钥的概念,除了以下特定的密钥&#34;。如果TypeScript具有真实subtraction types,您可以引用类似string - "isFormOK"
的类型并使用它来构建所需的类型。但这目前无法直接实现。
@Aaron提出的交集类型可能是可以接受的,但是如上所述有一些警告,并且在某些情况下依赖于类型检查器并不是真正彻底。如果这对你有用,那很好。否则,请继续阅读:
另一种可能性是使用literal subtraction types来解决缺少一般减法类型的问题,这些类型确实存在于TypeScript中。除了以下&#34;之外,您没有说&#34;所有可能的键,除了以下&#34;之外,您说&#34;来自此特定列表的所有键 。在这种情况下的想法是使ComponentType
通用并依赖于一些属性键集:
type ComponentState<K extends keyof any> =
{ [P in K | "isFormOK"]: P extends "isFormOK" ? boolean : FieldState }
(这使用了TypeScript 2.8中引入的conditional types,但如果需要,可以使用2.8之前的语法获得类似内容。)在上文中,您说ComponentState<K>
是K
取决于某些键K
,并且对于FieldState
中的每个值,属性类型为"isFormOK"
,除非值为boolean
,在这种情况下类型为{{} 1}}。
这种泛型类型比普通类型更麻烦,但是你可以这样做。以下代码仅接受可分配给某些ComponentState<K>
的输入,并返回相同的类型:
function asComponentState<C extends ComponentState<keyof C>>(componentState: C): C {
return componentState;
}
约束C extends ComponentState<keyof C>
强制类型检查器检查您传入的任何键的类型为C
,并将其用作适用于文字类型减法的特定键列表。让我们看看它是否有效:
const state = asComponentState({
isFormOK: true,
fieldA: {
value: 'abc',
status: 'ok',
errorText: ''
},
}); // okay, ComponentState<"isFormOK" | "fieldA">
const missingKey = asComponentState({
fieldA: {
value: 'abc',
status: 'ok',
errorText: ''
}
}); // error, Property 'isFormOK' is missing
const wrongFieldState = asComponentState({
isFormOK: true,
fieldA: {
value: 'abc',
status: 'ok',
errorText: ''
},
fieldB: "whoops"
}); // error, 'fieldB' is incompatible, 'string' is not 'FieldState'.
对我好看!
再次:好处是类型检查器正在强制执行您想要的约束。缺点是每个对ComponentState
的引用都必须是通用的。
希望有所帮助。祝你好运!