【问题标题】:How to write a type guard that takes an object of generic type T and a string parameter and checks that the string is of type keyof T?如何编写一个类型保护,它接受一个泛型类型 T 的对象和一个字符串参数,并检查该字符串是否为 keyof T 类型?
【发布时间】:2020-03-18 00:25:21
【问题描述】:

我有一个MyObject 类型的对象,它有两个字符串属性。

interface MyObject {
  a: number,
  b: string,
}

const myObject = {
  a: 5,
  b: 'str'
}

然后我有一个接受字符串的函数,我希望能够访问由字符串参数指定的上述对象的属性。我想要做的是在访问属性之前使用类型保护来检查字符串是否是对象的键。有必要在这里做一些检查,因为参数只是一个字符串,对象没有索引签名。

如果我制作一个特定版本来检查这种特定类型的对象(MyObject),它可以工作:

// specific version
const isValidPropertyForMyObject = (property: string): property is keyof MyObject => Object.keys(myObject).indexOf(property) !== -1

const getProperty1 = (property: string) => {
  if (isValidPropertyForMyObject(property)) {
    myObject[property]
  }
}

但是,如果我希望能够传入具有泛型类型和字符串参数的对象,并检查该属性实际上是对象的键,该怎么办?这是我的尝试:

const isValidMethodForHandler = <T extends { [i: string]: any }>(handler: T) => (
  method: string
): method is keyof T => Object.keys(handler).indexOf(method) !== -1;


const getProperty = (property: string) => {
  // const acceptedProperties = ["a", "b"];
  // if (acceptedProperties.indexOf(property) !== -1) {
  //   myObject[property]
  // }

  if (isValidMethodForHandler(myObject)(property)) {
    myObject[property]
  }
}

问题出在类型保护中:

类型谓词的类型必须可分配给其参数的类型。 类型“keyof T”不可分配给类型“字符串”。 键入'字符串 |号码 |符号'不可分配给类型'字符串'。 类型“数字”不可分配给类型“字符串”。(2677)

【问题讨论】:

  • 这里有什么特别需要用户定义的类型保护的理由吗?使用in 操作符就更简单了:if (key in obj) { return obj[key] }
  • @wrsx 我不认为该解决方案有效,我只是在 TS Playground 中尝试过,如下: const getProperty3 = (property: string) => { if (property in myObject) { myObject[属性] } }
  • 没有索引签名是不可能的,但是您可以在示例中使用通用的索引签名:getProperty&lt;T extends { [i: string]: any }&gt;(property: string, obj: T)
  • 我看不出有什么区别:codesandbox.io/s/vigilant-hoover-b3v4h
  • @wrsx 不同的是我需要一个类型保护。换句话说,我希望能够优化传入参数的类型。您的解决方案只是从对象中获取属性,如果属性不在对象中,则返回 undefined。这是没有意义的,可以直接在调用代码中使用简单的 obj[property] 来完成。

标签: typescript


【解决方案1】:

答案基于 TypeScript 问题跟踪器 here 中的此线程。

上面问题中具体TypeScript错误的解释在this other question

我的示例代码的解决方案是:

const isValidMethodForHandler = <T extends { [i: string]: any }>(handler: T) => (
  method: string
): method is Extract<keyof T, string> => Object.keys(handler).indexOf(method) !== -1;

const getProperty = (property: string) => {
  if (isValidMethodForHandler(myObject)(property)) {
    myObject[property]
  }

keyof 返回所有已知键,这些键的类型为string | number | symbol

要仅获取字符串属性,请使用Extract

【讨论】:

    【解决方案2】:

    你当然可以写:

    interface MyObject {
      a: number,
      b: string,
    }
    
    const myObject = {
      a: 5,
      b: 'str'
    }
    const isValidPropertyForMyObject = (property: string): property is keyof MyObject => 
         property in myObject;
    

    然后你可以这样做:

    const f = <K extends keyof MyObject>(obj: MyObject, k: K): MyObject[K] => obj[k];
    
    const g = <K extends keyof MyObject>(obj: MyObject, k: string | K): MyObject[K] | string =>
        isValidPropertyForMyObject(k) ? f(obj, k) : "NOT LEGAL";
    

    假设有一个常量myObject 在运行时列出MyObject 的所有键。

    【讨论】:

    • 我正在寻找一个完全不依赖 MyObject 的通用解决方案。您的解决方案特定于 MyObject,但在我的问题中,我已经有了一个更简单的解决方案。至少这是我从你的解决方案中理解的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-21
    • 1970-01-01
    • 2012-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多