简体   繁体   English

以类型安全的方式迭代 object 属性

[英]Iterate object properties in a type-safe manner

I have an immutable class Settings containing tons of members and want to provide a simple way to create a modified copy.我有一个不可变的class Settings ,其中包含大量成员,并希望提供一种简单的方法来创建修改后的副本。 I started with something like我从类似的东西开始

class Settings {
    private constructor(public readonly a: number, 
        public readonly b: number) {
    }

    static newSettings() {
        return new Settings(1, 2);
    }

    withA(a: number) {
        return new Settings(a, this.b);
    }

    withB(a: number) {
        return new Settings(this.a, b);
    }
}

which is exactly how I do it in Java (with Lombok generating all the boilerplate).这正是我在 Java 中的做法( Lombok生成所有样板文件)。 This doesn't scale well, so I switched to这不能很好地扩展,所以我切换到

interface ISettings {
    readonly a?: number
    readonly b?: number
}

class Settings implements ISettings {
    readonly a: number = 1
    readonly b: number = 2

    private constructor(base?: ISettings, overrides?: ISettings) {
        for (const k of Object.keys(this)) {
            // @ts-ignore
            this[k] = overrides?.[k] ?? base?.[k] ?? this[k]; // <---- PROBLEM
        }
    }

    static newSettings() {
        return new Settings();
    }

    with(overrides: ISettings) {
        return new Settings(this, overrides);
    }
}

It works, but I had to use @ts-ignore for one line, which IMHO should work out of the box.它可以工作,但我必须在一行中使用@ts-ignore ,恕我直言,它应该开箱即用。

I know, I could use eg, immerjs (offering this and more), but how can I get the typings right ?我知道,我可以使用例如immerjs (提供这个以及更多),但我怎样才能正确输入

The source of the issue问题的来源

First of all Object.keys will always return string type, because of the TS structural typing首先Object.keys将始终返回字符串类型,因为 TS 结构类型

The basic rule for TypeScript's structural type system is that x is compatible with y if y has at least the same members as x TypeScript 结构类型系统的基本规则是,如果 y 至少具有与 x 相同的成员,则 x 与 y 兼容

Generally it means that TS allows for subtypes to be passed, in other words, value with more members/fields can be passed, what means Object.keys will have more keys than we think.通常这意味着 TS 允许传递子类型,换句话说,可以传递具有更多成员/字段的值,这意味着Object.keys将具有比我们想象的更多的键。

Example of such behavior:这种行为的例子:

type X = { a: string };
type Y = X & { b: string };

function f(x: X) {
  return Object.keys(x);
}

const y = { a: 'a', b: 'b' };
f(y); // no error - works but we have more keys then X has

Workaround解决方法

I would go into such approach:我会将 go 变成这样的方法:

private constructor(base?: ISettings, overrides?: ISettings) {
    const keys = Object.keys(this) as (keyof ISettings)[]
    for (const k of keys) {
      // k is a | b
      this[k] = overrides?.[k] ?? base?.[k] ?? this[k];
    }
  }

Because of the behavior of Object.keys the only way here is to make type assertion by as .由于Object.keys的行为,这里唯一的方法是通过as进行类型断言。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM