Typescript按字符串属性名称安全更新属性

Typescript按字符串属性名称安全更新属性,typescript,keyof,typescript3.8,Typescript,Keyof,Typescript3.8,我需要用字符串属性名更新类属性的值。我首先通过以下方法确保属性名称有效: 将类客户端导出到{ ... 静态isValidPropertyName(名称:string):名称是ClientDTO的键{ return(名称为keyof ClientDTO)!==未定义 } } 然后在另一节课上我做了这个: foo(key:string,newValue:string){ 如果(!ClientDTO.isValidPropertyName(键)){ 返回 } if(newValue!==此.orig

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

将类客户端导出到{
...
静态isValidPropertyName(名称:string):名称是ClientDTO的键{
return(名称为keyof ClientDTO)!==未定义
}
}
然后在另一节课上我做了这个:

foo(key:string,newValue:string){
如果(!ClientDTO.isValidPropertyName(键)){
返回
}
if(newValue!==此.originalClient[键]){
//@ts忽略
this.originalClient[key]=newValue
}
}
查找现在运行得很好,但要进行更新,我必须将
/@ts ignore
放在那里,我真的很想知道如何正确地执行此操作,而不必将ignore放在那里

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


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

return(名称为keyof ClientDTO)!=未定义
这不会检查
name
是否是ClientDTO的键。它断言它是,然后检查字符串是否未定义。在厨房里试试看

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

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

class-MyClass{
建造师(
公共a:字符串,
公共b:字符串,
公共c:字符串,
公共x:号码,
公众y:号码,,
公共z:number){}
}
函数isStringProperty(propertyName:string):propertyName是“a”|“b”|“c”{
返回[“a”、“b”、“c”]。indexOf(propertyName)>=0;
}
函数isNumberProperty(propertyName:string):propertyName是“x”|“y”|“z”{
返回[“x”、“y”、“z”]。indexOf(propertyName)>=0;
}
函数setString(dto:MyClass,key:string,newValue:string){
if(isStringProperty(键)){
dto[键]=新值;
}
}
函数setNumber(dto:MyClass,key:string,newValue:number){
如果(isNumberProperty(键)){
dto[键]=新值;
}
}

另请参见:

return(名称为keyof ClientDTO)!=未定义
这不会检查
name
是否是ClientDTO的键。它断言它是,然后检查字符串是否未定义。在厨房里试试看

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

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

class-MyClass{
建造师(
公共a:字符串,
公共b:字符串,
公共c:字符串,
公共x:号码,
公众y:号码,,
公共z:number){}
}
函数isStringProperty(propertyName:string):propertyName是“a”|“b”|“c”{
返回[“a”、“b”、“c”]。indexOf(propertyName)>=0;
}
函数isNumberProperty(propertyName:string):propertyName是“x”|“y”|“z”{
返回[“x”、“y”、“z”]。indexOf(propertyName)>=0;
}
函数setString(dto:MyClass,key:string,newValue:string){
if(isStringProperty(键)){
dto[键]=新值;
}
}
函数setNumber(dto:MyClass,key:string,newValue:number){
如果(isNumberProperty(键)){
dto[键]=新值;
}
}


另请参见:

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

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
。因此,将您的type guard仅限于
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
    }
}
键入带有字符串值的键={
[K in keyof T]:T[K]扩展字符串?K:从不;
}[keyof T];
类ClientDTO{
/* ... */
静态isValidPropertyName(名称:string):名称为KeysWithStringValue{
//确保用实际执行的代码替换此代码
//上述限制。
返回名称!==未定义
}
}


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

isValidPropertyName(name: string): name is keyof ClientDTO { ... }
防止
ClientDTO的任何键进入
,因此当您尝试使用它时:

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

要解决此问题,请注意