如何键入检查此对象而不使用{}或任何?

时间:2018-05-06 10:05:06

标签: typescript

我有一个这样的对象:

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
}

如果您只是将interfacetype交换,那么它适用于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的类型并没有传达这个对象的真实模式的消息。

那么如何在不使用stateisFormOK的情况下键入fieldA {}对象,包括anyTask.Run

2 个答案:

答案 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"

Example in Playground

编辑:对象文字分配问题:

正如 @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

编辑2:映射类型

根据上面链接的问题,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的引用都必须是通用的。

希望有所帮助。祝你好运!