【问题标题】:Typescript type safe update of property by string property name通过字符串属性名称对属性进行类型安全更新
【发布时间】:2020-05-08 23:59:09
【问题描述】:

我需要通过字符串属性名称更新类的属性值。我首先通过此方法确保属性名称有效:

export class ClientDTO {
    ...

    static isValidPropertyName(name: string): name is keyof ClientDTO {
        return (name as keyof ClientDTO) !== undefined
    }
}

然后在另一个班级我正在这样做:

foo(key: string, newValue: string) {
  if (!ClientDTO.isValidPropertyName(key)) {
    return
  }

  if (newValue !== this.originalClient[key]) {
    // @ts-ignore
    this.originalClient[key] = newValue
  }
}

查找现在运行良好,但要进行更新,我必须将 // @ts-ignore 放在那里,我真的很想弄清楚如何正确地做到这一点,而不必在那里忽略。

我开启了严格的检查,所以我得到了错误

TS2322:类型“any”不可分配给类型“never”

【问题讨论】:

  • //@ts-ignore 被删除时会出现什么错误?
  • TS2322:类型“any”不可分配给类型“never”
  • 您确定name as keyof ClientDTO 在运行时验证任何内容吗? ClientDTO 是否有任何非string 属性?
  • 我假设this.originalClientClientDTO 的一个实例,对吧?如果是这样,它上面的所有实例属性都是string 类型的吗?还是有多种类型?
  • 是的,它是一个 ClientDTO。这是多种类型的混合。字符串、布尔值、日期和数字。

标签: typescript keyof typescript3.8


【解决方案1】:
return (name as keyof ClientDTO) !== undefined

这不会检查name 是否是ClientDTO 的键。它断言它是,然后检查字符串是否未定义。在playground 中尝试一下。

即使这样有效,它也只会检查字符串是否是 ClientDTO 的有效键,但不会说明它是哪一个。因此,Typescript 会检查您设置的类型是否可以安全地分配给 ClientDTO 的 any 键;由于 ClientDTO 包含“混合类型”,包括“字符串、布尔值、日期和数字”,因此分配的唯一安全值是 never

为了安全地分配newValue: string,您需要一个函数来确保在运行时您的key 用于string 类型的属性,这可能涉及一些重复。

class MyClass {
    constructor(
        public a: string,
        public b: string,
        public c: string,
        public x: number,
        public y: number,
        public z: number) { }
}

function isStringProperty(propertyName: string): propertyName is "a" | "b" | "c" {
    return ["a", "b", "c"].indexOf(propertyName) >= 0;
}

function isNumberProperty(propertyName: string): propertyName is "x" | "y" | "z" {
    return ["x", "y", "z"].indexOf(propertyName) >= 0;
}

function setString(dto: MyClass, key: string, newValue: string) {
    if (isStringProperty(key)) {
        dto[key] = newValue;
    }
}

function setNumber(dto: MyClass, key: string, newValue: number) {
    if (isNumberProperty(key)) {
        dto[key] = newValue;
    }
}

typescript playground

另见:Typescript error:Type 'number' is not assignable to type 'never'

【讨论】:

  • 谢谢。我不知道 typescriptlang.org/play,太棒了!
【解决方案2】:

问题在于您的自定义类型保护:

isValidPropertyName(name: string): name is keyof ClientDTO { ... }

正在防范ClientDTO的任何键,所以当你尝试使用它时:

this.originalClient[key] = newValue // newValue is type string

TypeScript 尝试为 this.originalClient[key] 的值推断正确的类型。由于key 可以是ClientDTO任何 键,因此您分配给它的值必须可分配给这些键的所有值类型。由于这些键的值类型混合在一起,因此唯一可分配给它们的类型是底部类型never;无法为其分配任何内容的类型,因此会出现错误。

要解决此问题,请注意您将newValue 输入string。因此,将您的类型保护限制为只有 ClientDTO 的值是字符串的那些键:

type KeysWithStringValues<T extends {}> = {
    [K in keyof T]: T[K] extends string ? K : never;
}[keyof T];

class ClientDTO {
    /* ... */
    static isValidPropertyName(name: string): name is KeysWithStringValues<ClientDTO> {
        // Make sure to replace this with code that ACTUALLY enforces
        // the above constraint.
        return name !== undefined
    }
}

Playground Link.

【讨论】:

  • 这很酷,但当您开始添加可选类型时似乎效果不佳。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-10-13
  • 2011-10-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-12
  • 2012-05-04
相关资源
最近更新 更多