强制执行 Typescript 对象的索引成员的类型?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13315131/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-11 00:55:59  来源:igfitidea点击:

Enforcing the type of the indexed members of a Typescript object?

typescript

提问by Armentage

I would like to store a mapping of string -> string in a Typescript object, and enforce that all of the keys map to strings. For example:

我想在 Typescript 对象中存储 string -> string 的映射,并强制所有键都映射到字符串。例如:

var stuff = {};
stuff["a"] = "foo";   // okay
stuff["b"] = "bar";   // okay
stuff["c"] = false;   // ERROR!  bool != string

Is there a way for me to enforce that the values must be strings (or whatever type..)?

有没有办法让我强制执行值必须是字符串(或任何类型..)?

回答by Ryan Cavanaugh

var stuff: { [key: string]: string; } = {};
stuff['a'] = ''; // ok
stuff['a'] = 4;  // error

// ... or, if you're using this a lot and don't want to type so much ...
interface StringMap { [key: string]: string; }
var stuff2: StringMap = { };
// same as above

回答by Sandy Gifford

interface AgeMap {
    [name: string]: number
}

const friendsAges: AgeMap = {
    "Sandy": 34,
    "Joe": 28,
    "Sarah": 30,
    "Michelle": "fifty", // ERROR! Type 'string' is not assignable to type 'number'.
};

Here, the interface AgeMapenforces keys as strings, and values as numbers. The keyword namecan be any identifier and should be used to suggest the syntax of your interface/type.

在这里,接口将AgeMap键强制为字符串,将值强制为数字。关键字name可以是任何标识符,应用于建议您的接口/类型的语法。

You can use a similar syntax to enforce that an object has a key for every entry in a union type:

您可以使用类似的语法来强制对象具有联合类型中每个条目的键:

type DayOfTheWeek = "sunday" | "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday";

type ChoresMap = { [day in DayOfTheWeek]: string };

const chores: ChoresMap = { // ERROR! Property 'saturday' is missing in type '...'
    "sunday": "do the dishes",
    "monday": "walk the dog",
    "tuesday": "water the plants",
    "wednesday": "take out the trash",
    "thursday": "clean your room",
    "friday": "mow the lawn",
};

You can, of course, make this a generic type as well!

当然,您也可以将其设为通用类型!

type DayOfTheWeek = "sunday" | "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday";

type DayOfTheWeekMap<T> = { [day in DayOfTheWeek]: T };

const chores: DayOfTheWeekMap<string> = {
    "sunday": "do the dishes",
    "monday": "walk the dog",
    "tuesday": "water the plants",
    "wednesday": "take out the trash",
    "thursday": "clean your room",
    "friday": "mow the lawn",
    "saturday": "relax",
};

const workDays: DayOfTheWeekMap<boolean> = {
    "sunday": false,
    "monday": true,
    "tuesday": true,
    "wednesday": true,
    "thursday": true,
    "friday": true,
    "saturday": false,
};

10.10.2018 update:Check out @dracstaxi's answer below - there's now a built-in type Recordwhich does most of this for you.

10.10.2018 更新:查看下面@dracstaxi 的回答 - 现在有一个内置类型Record可以为您完成大部分工作。

1.2.2020 update:I've entirely removed the pre-made mapping interfaces from my answer. @dracstaxi's answer makes them totally irrelevant. If you'd still like to use them, check the edit history.

1.2.2020 更新:我已从答案中完全删除了预制的映射界面。@dracstaxi 的回答使它们完全无关紧要。如果您仍想使用它们,请检查编辑历史记录。

回答by dracstaxi

A quick update: since Typescript 2.1 there is a built in type Record<T, K>that acts like a dictionary.

快速更新:从 Typescript 2.1 开始,有一个Record<T, K>像字典一样的内置类型。

An example from docs:

来自文档的一个例子:

// For every properties K of type T, transform it to U
function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U>

const names = { foo: "hello", bar: "world", baz: "bye" };
const lengths = mapObject(names, s => s.length);  // { foo: number, bar: number, baz: number }

TypeScript 2.1 Documentation on Record<T, K>

TypeScript 2.1 文档 Record<T, K>

The only disadvantage I see to using this over {[key: T]:Kis that you can encode useful info on what sort of key you are using in place of "key" e.g. if your object only had prime keys you could hint at that like so: {[prime: number]: yourType}.

唯一的缺点我看到使用这种过度{[key: T]:K的是,你可以在编码排序关键字是什么您使用的是地方的“钥匙”有用的信息例如,如果你的对象只有黄金键,你可以在那像这样的提示:{[prime: number]: yourType}

Here's a regex I wrote to help with these conversions. This will only convert cases where the label is "key". To convert other labels simply change the first capturing group:

这是我为帮助这些转换而编写的正则表达式。这只会转换标签为“key”的情况。要转换其他标签,只需更改第一个捕获组:

Find: \{\s*\[(key)\s*(+\s*:\s*(\w+)\s*\]\s*:\s*([^\}]+?)\s*;?\s*\}

找: \{\s*\[(key)\s*(+\s*:\s*(\w+)\s*\]\s*:\s*([^\}]+?)\s*;?\s*\}

Replace: Record<$2, $3>

代替: Record<$2, $3>

回答by shabunc

@Ryan Cavanaugh's answer is totally ok and still valid. Still it worth to add that as of Fall'16 when we can claim that ES6 is supported by the majority of platforms it almost always better to stick to Map whenever you need associate some data with some key.

@Ryan Cavanaugh 的回答完全没问题,而且仍然有效。仍然值得补充的是,从 16 年秋季开始,当我们可以声称大多数平台都支持 ES6 时,每当您需要将某些数据与某个键关联时,坚持使用 Map 几乎总是更好。

When we write let a: { [s: string]: string; }we need to remember that after typescript compiled there's not such thing like type data, it's only used for compiling. And { [s: string]: string; } will compile to just {}.

我们在写的let a: { [s: string]: string; }时候要记住,typescript编译后就没有type data之类的东西了,它只是用来编译的。和 { [s: string]: string; } 将编译为仅 {}。

That said, even if you'll write something like:

也就是说,即使你会写这样的东西:

class TrickyKey  {}

let dict: {[key:TrickyKey]: string} = {}

This just won't compile (even for target es6, you'll get error TS1023: An index signature parameter type must be 'string' or 'number'.

这只是不会编译(即使对于target es6,你会得到error TS1023: An index signature parameter type must be 'string' or 'number'.

So practically you are limited with string or number as potential key so there's not that much of a sense of enforcing type check here, especially keeping in mind that when js tries to access key by number it converts it to string.

所以实际上你被限制为字符串或数字作为潜在的键,所以这里没有太多强制类型检查的感觉,特别是要记住,当 js 尝试按数字访问键时,它会将其转换为字符串。

So it is quite safe to assume that best practice is to use Map even if keys are string, so I'd stick with:

因此,即使键是字符串,也可以假设最佳实践是使用 Map 是非常安全的,所以我会坚持:

let staff: Map<string, string> = new Map();

回答by Lasithds

Define interface

定义接口

interface Settings {
  lang: 'en' | 'da';
  welcome: boolean;
}

Enforce key to be a specific key of Settings interface

强制 key 为 Settings 界面的特定 key

private setSettings(key: keyof Settings, value: any) {
   // Update settings key
}

回答by Roy Art

Building on @shabunc's answer, this would allow enforcing either the key or the value — or both — to be anything you want to enforce.

以@shabunc 的回答为基础,这将允许将键或值(或两者)强制为您想要强制执行的任何内容。

type IdentifierKeys = 'my.valid.key.1' | 'my.valid.key.2';
type IdentifierValues = 'my.valid.value.1' | 'my.valid.value.2';

let stuff = new Map<IdentifierKeys, IdentifierValues>();

Should also work using enuminstead of a typedefinition.

也应该使用enum而不是type定义来工作。