是否强制使用Typescript对象的索引成员类型?

Arm*_*age 214 typescript

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

var stuff = {};
stuff["a"] = "foo";   // okay
stuff["b"] = "bar";   // okay
stuff["c"] = false;   // ERROR!  bool != string
Run Code Online (Sandbox Code Playgroud)

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

Rya*_*ugh 391

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
Run Code Online (Sandbox Code Playgroud)

  • `number`也允许作为索引类型 (35认同)
  • 这种语法的有趣之处在于键的"字符串"以外的任何类型实际上都是错误的.鉴于JS映射是由字符串明确地键入的,但它确实使语法有些多余,因此具有完美的意义.像'{}:string'这样简单地指定值的类型似乎更简单,除非它们以某种方式添加以允许自动强制键作为生成代码的一部分. (10认同)
  • @RyanCavanaugh是否有可能(或将来)使用`type Keys ='Acceptable'| 'String'| 'Keys'作为索引(键)类型? (6认同)
  • 不,它确实强制执行密钥类型.即使myObject有一个很好的toString()实现,你也无法做[myObject] ='what'. (3认同)
  • 值得注意的是:编译器只强制执行值类型,而不是密钥类型.你可以做什么[15] ='什么',它不会抱怨. (2认同)
  • 将其称为 StringMap 是否明智?这里不是真正的本地地图 (2认同)
  • @YakovFain我认为这是因为Javascript中有一个奇怪的怪癖,其中`1 =="1"`.索引器也遵循这些规则 - "["hello","world"] ["1"]`返回`world`和`({"1":"foo","2":"bar"})[2 ]`返回`bar`. (2认同)
  • 注意`{number:string}`,因为即使这可能在赋值时强制执行索引的类型,对象仍然在内部将键存储为`string`.这实际上可能会混淆TypeScript和破坏类型的安全性.例如,如果你试图通过用值交换键将`{number:string}`转换为`{string:number}`,你实际上最终会得到`{string:string}`但TypeScript不会抛出任何警告/错误 (2认同)

San*_*ord 86

无论我走到哪里,我随身携带这个文件

export interface StringTMap<T> { [key: string]: T; };
export interface NumberTMap<T> { [key: number]: T; };

export interface StringAnyMap extends StringTMap<any> {};
export interface NumberAnyMap extends NumberTMap<any> {};

export interface StringStringMap extends StringTMap<string> {};
export interface NumberStringMap extends NumberTMap<string> {};

export interface StringNumberMap extends StringTMap<number> {};
export interface NumberNumberMap extends NumberTMap<number> {};

export interface StringBooleanMap extends StringTMap<boolean> {};
export interface NumberBooleanMap extends NumberTMap<boolean> {};
Run Code Online (Sandbox Code Playgroud)

StringTMap并且NumberTMap是泛型,可用于创建任何类型的地图(via let myTypeMap: StringTMap<MyType> = {}).其余的是常见文字类型之间有用的预定义映射.

这里的重要语法是{ [key: string]: T; }表示接口使用类型的键string(该单词key可以是任何标识符,并且应该用于指示键的重要性)和类型值来强制实施对象文字T.如果你想string为键和boolean值构造一个带有"名称" 的对象(而不是使用上面的继承),它的接口就是{ [name: string]: boolean }.

10.10.2018更新: 查看下面@dracstaxi的答案 - 现在有一个内置类型Record,它或多或少地完成了这个.

  • { [键:数字]:T;} 不是类型安全的,因为在内部对象的键*始终*是一个字符串 - 有关更多详细信息,请参阅@tep 对问题的评论。例如运行`x = {}; x[1] = 2;` 在 Chrome 中,`Object.keys(x)` 返回 ["1"] 并且 `JSON.stringify(x)` 返回 '{"1":2}'。具有“Number”类型的极端情况(例如 Infinity、NaN、1e300、999999999999999999999 等)将转换为字符串键。还要注意字符串键的其他极端情况,例如 `x[''] = 'empty string';`、`x['000'] = ' Threezeros';` `x[undefined] = 'foo'`。 (3认同)

dra*_*axi 57

快速更新:自Typescript 2.1以来,内置类型Record<T, K>就像字典一样.

来自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 }
Run Code Online (Sandbox Code Playgroud)

TypeScript 2.1文档 Record<T, K>

我看到使用它的唯一缺点{[key: T]:K是你可以编码关于你使用什么类型的键来代替"键"的有用信息,例如,如果你的对象只有你可以暗示的主键那样:{[prime: number]: yourType}.

这是我为帮助完成这些转换所写的正则表达式.这只会转换标签为"key"的情况.要转换其他标签,只需更改第一个捕获组:

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

更换: Record<$2, $3>

  • 这应该获得更多支持-本质上是我的答案的较新的本地版本。 (3认同)
  • 值得一提的是,字符串联合也可以在 `Record` 中使用: `const isBroken: Record&lt;"hat" | “茶壶”| "cup", boolean&gt; = { 帽子: false, 杯子: false, 茶壶: true };` (2认同)

Iva*_*nko 39

实际上有一个内置实用程序 Record

    const record: Record<string, string> = {};
    record['a'] = 'b';
    record[1] = 'c'; // leads to typescript error
    record['d'] = 1; // leads to typescript error
Run Code Online (Sandbox Code Playgroud)


Las*_*hds 9

定义接口

interface Settings {
  lang: 'en' | 'da';
  welcome: boolean;
}
Run Code Online (Sandbox Code Playgroud)

强制 key 为 Settings 界面的特定 key

private setSettings(key: keyof Settings, value: any) {
   // Update settings key
}
Run Code Online (Sandbox Code Playgroud)


sha*_*unc 8

@Ryan Cavanaugh的回答完全没问题且仍然有效.值得一提的是,截至2016年秋季,当我们声称ES6得到了大多数平台的支持时,无论何时需要将某些数据与某些键相关联,最好坚持使用Map.

当我们编写时,let a: { [s: string]: string; }我们需要记住,在编译打字稿后,不存在类型数据这样的东西,它只用于编译.并且{[s:string]:string; 将编译为{}.

也就是说,即使你写的东西如下:

class TrickyKey  {}

let dict: {[key:TrickyKey]: string} = {}
Run Code Online (Sandbox Code Playgroud)

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

所以实际上你被限制为字符串或数字作为潜在的密钥所以在这里没有那么多强制类型检查的意义,特别要记住,当js尝试按编号访问密钥时,它将其转换为字符串.

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

let staff: Map<string, string> = new Map();
Run Code Online (Sandbox Code Playgroud)

  • 发布这个答案时不确定这是否可行,但今天你可以这样做:`let dict:{[key in TrickyKey]:string} = {}` - 其中`TrickyKey`是一个字符串文字类型(例如` Foo"|"Bar"`). (4认同)

Ame*_*icA 5

您可以将名称传递给未知键,然后编写您的类型:

type StuffBody = {
  [key: string]: string;
};
Run Code Online (Sandbox Code Playgroud)

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

let stuff: StuffBody = {};
Run Code Online (Sandbox Code Playgroud)

但是对于FlowType不需要有名字:

type StuffBody = {
  [string]: string,
};
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

149288 次

最近记录:

6 年,8 月 前