我想将 string -> string 的映射存储在 Typescript 对象中,并强制所有键映射到字符串。例如:
var stuff = {};
stuff["a"] = "foo"; // okay
stuff["b"] = "bar"; // okay
stuff["c"] = false; // ERROR! bool != string
有没有办法让我强制这些值必须是字符串(或任何类型..)?
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
可以是任何标识符,应用于建议您的接口/类型的语法。
您可以使用类似的语法来强制对象对联合类型中的每个条目都有一个键:
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,
};
2018 年 10 月 10 日更新: 查看下面的 @dracstaxi 答案 - 现在有一个内置类型 Record
可以为您完成大部分工作。
1.2.2020 更新:我已经从我的答案中完全删除了预制的映射接口。 @dracstaxi 的回答使它们完全无关紧要。如果您仍想使用它们,请查看编辑历史记录。
x = {}; x[1] = 2;
,然后 Object.keys(x)
返回 ["1"] 并且 JSON.stringify(x)
返回 '{"1":2}'。 typeof Number
的极端情况(例如 Infinity、NaN、1e300、9999999999999999999999 等)被转换为字符串键。还要注意字符串键的其他极端情况,例如 x[''] = 'empty string';
、x['000'] = 'threezeros';
x[undefined] = 'foo'
。
{ [name: string]: [age: number] }
包括数字值是年龄的提示? @桑迪吉福德
快速更新:从 Typescript 2.1 开始,有一个内置类型 Record<T, K>
,其作用类似于字典。
在这种情况下,您可以声明如下内容:
var stuff: Record<string, any> = {};
您还可以通过联合文字类型来限制/指定潜在的键:
var stuff: Record<'a'|'b'|'c', string|boolean> = {};
这是使用文档中的记录类型的更通用示例:
// 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>
我看到在 {[key: T]: K}
上使用它的唯一缺点是,您可以对有用的信息进行编码,说明您正在使用哪种键来代替“键”,例如,如果您的对象只有主键,您可以像这样提示:{ 2}。
这是我为帮助进行这些转换而编写的正则表达式。这只会转换标签为“key”的情况。要转换其他标签,只需更改第一个捕获组:
查找:\{\s*\[(key)\s*(+\s*:\s*(\w+)\s*\]\s*:\s*([^\}]+?)\s*;?\s*\}
替换:Record<$2, $3>
{}
吗?
Record
(与 Javascript Map
不同)仅提供一种强制对象字面量内容的方法。您不能 new Record...
并且 const blah: Record<string, string>;
将编译为 const blah;
。
Record
:const isBroken: Record<"hat" | "teapot" | "cup", boolean> = { hat: false, cup: false, teapot: true };
您可以将名称传递给未知键,然后编写您的类型:
type StuffBody = {
[key: string]: string;
};
现在您可以在类型检查中使用它:
let stuff: StuffBody = {};
但是对于 FlowType,不需要名称:
type StuffBody = {
[string]: string,
};
实际上有一个内置实用程序 Record:
const record: Record<string, string> = {};
record['a'] = 'b';
record[1] = 'c'; // leads to typescript error
record['d'] = 1; // leads to typescript error
定义接口
interface Settings {
lang: 'en' | 'da';
welcome: boolean;
}
强制键为设置界面的特定键
private setSettings(key: keyof Settings, value: any) {
// Update settings key
}
@Ryan Cavanaugh 的回答完全可以并且仍然有效。仍然值得补充的是,截至 16 年秋季,当我们可以声称大多数平台都支持 ES6 时,当您需要将某些数据与某些键相关联时,几乎总是更好地坚持 Map。
当我们写 let a: { [s: string]: string; }
时,我们需要记住,typescript 编译后没有类型数据之类的东西,它只是用于编译。和 { [s: string]: string; } 将编译为 {}。
也就是说,即使你会写这样的东西:
class TrickyKey {}
let dict: {[key:TrickyKey]: string} = {}
这只是无法编译(即使对于 target es6
,您也会得到 error TS1023: An index signature parameter type must be 'string' or 'number'.
因此,实际上您受限于字符串或数字作为潜在键,因此这里没有太多强制类型检查的意义,特别是请记住,当 js 尝试按数字访问键时,它会将其转换为字符串。
因此,即使键是字符串,也可以假设最佳做法是使用 Map 是非常安全的,所以我会坚持:
let staff: Map<string, string> = new Map();
let dict: {[key in TrickyKey]: string} = {}
- 其中 TrickyKey
是字符串文字类型(例如 "Foo" | "Bar"
)。
Map
对象几乎总是更好的想法。地图会带来额外的内存开销,并且(更重要的是)需要从存储为 JSON 字符串的任何数据手动实例化。它们通常非常有用,但不仅仅是为了从中获取类型。
基于@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
定义。
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];
}
}
如果您了解这个概念,请发表评论
tmpParams[customkey]
具有适当的值?不只是 any
吗?
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!');
number
也允许作为索引类型type Keys = 'Acceptable' | 'String' | 'Keys'
作为索引(键)类型?{ number: string }
,因为即使这可能会在分配时强制执行索引的类型,但该对象仍然在内部将键存储为string
。这实际上会混淆 TypeScript 并破坏类型安全。例如,如果您尝试通过将键与值交换来将{ number: string }
转换为{ string: number }
,您实际上会得到{ string: string }
,但 TypeScript 不会引发任何警告/错误