获取JavaScript接口键作为字符串(类似于枚举)

huangapple go评论113阅读模式
英文:

Get Javascript interface keys as string (like an Enum)

问题

我的请求实际上非常简单,但我几乎找不到类似的内容。想象一下,你有一个如下所示的接口:

interface MyInterface: {
  propertyA: number;
  propertyB: string;
  propertyC: boolean;
}

现在你想要动态访问字段的值,可以像这样做:object['propertyA'],其中object的类型是MyInterface

然而,将'propertyA'作为原始字符串传递是不实际且危险的,因为如果你重新命名它或更改接口,代码将无法工作(并且它不会被 TypeScript 捕获,但我不确定这是否可能)。

理想情况下,我希望能够像这样做 MyInterface.propertyA,我感到失望的是似乎这种方式并不存在,我不明白为什么它不应该存在。

现在,在你提到object.propertyA的时候,我已经简化了这个概念以便易于理解,但实际用例并不完全相同。我可能会从 React 组件中将接口的字段作为字符串传递到 JSX 中,以利用可重用的方法,或者我可能会检查字符串是否与接口中的某些键匹配,以相应地显示错误。

从接口/类自动生成枚举不是一个坏的解决方案,尽管不是理想的解决方案,因为它仍然会带来一些开销,但只要它不需要在接口更改时进行更新,并且不依赖于接口属性的类型,那就还可以接受。

英文:

My request is actually really simple but I have found almost nothing of the sort online. Picture this, you have an interface like so:

interface MyInterface: {
  propertyA: number;
  propertyB: string;
  propertyC: boolean;
}

Now you want to use the name of a field to access its value dynamically so you do something like object['propertyA'], where object is of type MyInterface.

However, passing 'propertyA' as a raw string is impractical and dangerous because if you rename it or change the interface the code won't work. (And it doesn't get picked up by typescript but I'm not sure this is possible anyway).

Ideally, I would like to do something like this MyInterface.propertyA, I was saddened that this doesn't seem to exist, I don't see any reason why it shouldn't.

And now before you say that's what object.propertyA is for, I have simplified the concept to make it easy to understand but the use case is not exactly this. I might pass fields of an interface as strings from react components within the JSX to make use of a reusable method or I could be checking whether a string matches a subset of keys in an interface to display errors accordingly.

Autogenerating an enum from the interface/class is not a bad solution although not ideal as it still incurs an overhead but as long as it doesn't need to be updated each time the interface changes and it doesn't depend on the type of the interface's properties then that's not bad.

答案1

得分: 4

接口在运行时不存在,因此无法将接口的属性名称作为运行时值获取。但是,您可以采用另一种方法:创建一个“模型”对象:

const myInterfaceModel = {
    propertyA: 42,
    propertyB: "x",
    propertyC: true,
};

然后使用该对象来定义接口:

type MyInterface = typeof myInterfaceModel;

这将与您的问题中的 MyInterface 相同。

然后,您可以创建一个具有属性名称的字符串的对象,如下所示:

const myInterfaceKeys = Object.fromEntries(
    Object.keys(myInterfaceModel).map((key) => [key, key])
);

现在,如果我们仅仅这样做,myInterfaceKeys 将是(实际上)Record<string, string>,这对于开发体验不是很有用。但是,我们可以通过在其上使用映射类型来改进:

type KeyNames<T> = {
    [Key in keyof T]: Key;
};
// ...
const myInterfaceKeys = Object.fromEntries(
    Object.keys(myInterfaceModel).map((key) => [key, key])
) as KeyNames<MyInterface>;

现在,其类型与其运行时值匹配,我们可以获得有用的自动完成功能。

示例用法(只是一个基本的函数,但也可以是一个 React 组件):

function example(obj: MyInterface, key: keyof MyInterface) {
    console.log(obj[key]);
}

declare let exampleObject: MyInterface;

example(exampleObject, myInterfaceKeys.propertyA);

这是所有内容的综合示例(playground 链接):

type KeyNames<T> = {
    [Key in keyof T]: Key;
};

const myInterfaceModel = {
    propertyA: 42,
    propertyB: "x",
    propertyC: true,
};
type MyInterface = typeof myInterfaceModel;

const myInterfaceKeys = Object.fromEntries(
    Object.keys(myInterfaceModel).map((key) => [key, key])
) as KeyNames<MyInterface>;

myInterfaceKeys
// 在此之后键入一个句点以查看自动完成

通常,我会只使用字符串字面量(example(exampleObject, "propertyA")),因为 IDE 将提供适当的选项,而且由于 keyof MyInterface 是有限制的,如果删除一个属性,任何引用它的代码都将停止编译。但是,如果您希望拥有包含键的对象,可以执行上述操作来实现它。(另外,我刚刚尝试了一下,如果使用包含键的对象,重命名属性会在整个代码中起作用,而字符串字面量则不会。)

英文:

Interfaces don't exist at runtime, so there's no way to get the property names of an interface as runtime values. What you can do, though, is go the other way: Have a "model" object:

const myInterfaceModel = {
    propertyA: 42,
    propertyB: &quot;x&quot;,
    propertyC: true,
};

Then define the interface using that object:

type MyInterface = typeof myInterfaceModel;

That defines MyInterface just as it is in your question.

Then you can create an object that has the property names as strings, like this:

const myInterfaceKeys = Object.fromEntries(
    Object.keys(myInterfaceModel).map((key) =&gt; [key, key])
);

Now, if we just did that, myInterfaceKeys would be (effectively) Record&lt;string, string&gt;, which isn't useful for devex. But we can do better by using a mapped type on it:

type KeyNames&lt;T&gt; = {
    [Key in keyof T]: Key;
};
// ...
const myInterfaceKeys = Object.fromEntries(
    Object.keys(myInterfaceModel).map((key) =&gt; [key, key])
) as KeyNames&lt;MyInterface&gt;;

Now, its type matches its runtime value, and we get useful autocompletion:

获取JavaScript接口键作为字符串(类似于枚举)

Example usage (just a basic function, but could as easily be a React component):

function example(obj: MyInterface, key: keyof MyInterface) {
    console.log(obj[key]);
}

declare let exampleObject: MyInterface;

example(exampleObject, myInterfaceKeys.propertyA);

Here's that all together (playground link):

type KeyNames&lt;T&gt; = {
    [Key in keyof T]: Key;
};

const myInterfaceModel = {
    propertyA: 42,
    propertyB: &quot;x&quot;,
    propertyC: true,
};
type MyInterface = typeof myInterfaceModel;
//   ^? type MyInterface = { propertyA: number, propertyB: string; propertyC : boolean }

const myInterfaceKeys = Object.fromEntries(
    Object.keys(myInterfaceModel).map((key) =&gt; [key, key])
) as KeyNames&lt;MyInterface&gt;;

myInterfaceKeys
//             ^−−−− Type a period after this to see the autocompletion

Usually I'd just use string literals (example(exampleObject, &quot;propertyA&quot;)) since the IDE will offer the appropriate choices, and since keyof MyInterface is restrictive, if you remove a property, any code that referenced it with a string literal would stop compiling. But if you want that object with the keys on it, you can do the above to achieve that. (Also, I just tried, and if you use your object of keys, renaming a property works throughout, whereas the string literals didn't.)

答案2

得分: 1

你是否在寻找类似这样的东西?

enum Direction {
  Up = "u",
  Down = "d",
  Left = "l",
  Right = "r",
}

interface MyDirection  {
    [Direction.Up]: string;
    [Direction.Down]: number;
}

const obj: MyDirection = {
    [Direction.Up]: '向上',
    [Direction.Down]: 12,
}

console.log(obj[Direction.Down], Direction.Down)
英文:

Are you looking for something like this?

enum Direction {
  Up = &quot;u&quot;,
  Down = &quot;d&quot;,
  Left = &quot;l&quot;,
  Right = &quot;r&quot;,
}

interface MyDirection  {
    [Direction.Up]:string;
    [Direction.Down]:number;
}

const obj:MyDirection = {
    [Direction.Up]: &#39;up&#39;,
    [Direction.Down]:12,

}


console.log(obj[Direction.Down], Direction.Down)

huangapple
  • 本文由 发表于 2023年2月19日 00:55:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/75494881.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定