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

我想存储字符串->字符串,并强制所有的值都映射到字符串。例如:

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

是否有一种方法让我强制值必须是字符串(或任何类型..)?

591246 次浏览
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
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'.
};

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

你可以使用类似的语法来强制一个对象对union类型中的每个条目都有一个键:

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",
};

当然,您也可以将其设置为泛型类型!

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更新: 看看下面@dracstaxi的答案——现在有一个内置类型Record,它为你做了大部分工作

< >强1.2.2020更新: 我已经完全从我的回答中删除了预先制作的映射接口。@dracstaxi的回答让他们完全无关紧要。如果你仍然想使用它们,检查编辑历史

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

当我们编写let a: { [s: string]: string; }时,我们需要记住typescript编译后没有类型数据这样的东西,它只用于编译。和{[s:字符串]:字符串;}将编译为{}。

也就是说,即使你写的是:

class TrickyKey  {}


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

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

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

因此,假设最佳实践是使用Map,即使键是字符串,这是相当安全的,所以我坚持使用:

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

基于@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>();

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

一个快速更新:从Typescript 2.1开始,就有了一个内置的Record<T, K>类型,它的作用类似于字典。

在这种情况下,你可以这样声明:

var stuff: Record<string, any> = {};

你也可以通过联合文字类型来限制/指定潜在的键:

var stuff: Record<'a'|'b'|'c', string|boolean> = {};

下面是一个使用文档中的record类型的更通用的例子:

// 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关于Record<T, K>的文档

我认为使用{[key: T]: K}的唯一缺点是,你可以编码有用的信息,你正在使用什么类型的键来代替"key"例如,如果你的对象只有质键,你可以这样提示:{[prime: number]: yourType}

下面是我写的一个正则表达式来帮助这些转换。这将只转换标签为"key"的情况。要转换其他标签,只需更改第一个捕获组:

发现:# EYZ0

替换:# EYZ0

定义接口

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

强制键为设置界面的特定键

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

你可以传递一个名字给未知键,然后写你的类型:

type StuffBody = {
[key: string]: string;
};

现在你可以在你的类型检查中使用它:

let stuff: StuffBody = {};

但是对于FlowType,不需要有名称:

type StuffBody = {
[string]: string,
};
interface AccountSelectParams {
...
}
const params = { ... };


const tmpParams: { [key in keyof AccountSelectParams]: any } | undefined = {};
for (const key of Object.keys(params)) {
const customKey = (key as keyof typeof params);
if (key in params && params[customKey] && !this.state[customKey]) {
tmpParams[customKey] = params[customKey];
}
}

如果你明白这个概念,请评论

实际上有一个内置的实用工具记录:

    const record: Record<string, string> = {};
record['a'] = 'b';
record[1] = 'c'; // leads to typescript error
record['d'] = 1; // leads to typescript error
type KeyOf<T> = keyof T;


class SomeClass<T, R> {
onlyTFieldsAllowed = new Map<KeyOf<T>, R>();
}


class A {
myField = 'myField';
}


const some = new SomeClass<A, any>();


some.onlyTFieldsAllowed.set('myField', 'WORKS');
some.onlyTFieldsAllowed.set('noneField', 'Not Allowed!');