属性 字符串 属性 名称的打字稿类型安全更新

Typescript type safe update of property by string property name

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

export class ClientDTO {
    ...

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

然后在另一个 class 我这样做:

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

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

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

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

TS2322: Type 'any' is not assignable to type 'never'

return (name as keyof ClientDTO) !== undefined

这不会检查 name 是 ClientDTO 的键。它断言它是,然后检查该字符串是否未定义。在 playground.

中尝试

即使这样做有效,它也只会检查该字符串是否是 ClientDTO 的有效密钥,但不会说明它是哪一个。因此,Typescript 会检查您设置的类型是否可以安全地分配给 ClientDTO 的 any 键;由于 ClientDTO 包含 "a mix of types",其中包括 "String, Boolean, date and number",因此要分配的唯一安全值是 never.

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

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

另请参阅:

问题是你的自定义类型保护:

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

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

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

TypeScript 正在尝试为 this.originalClient[key] 的值推断正确的类型。由于 key 可以是 ClientDTOany 键,您分配给它的值必须可以分配给这些键的所有值类型。由于这些键混合了多种值类型,因此唯一可分配给所有这些键的类型是底部类型 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.