ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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


Z
ZephDavies
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

number 也允许作为索引类型
值得注意的是:编译器只强制执行值类型,而不是键类型。你可以做一些事情[15] = 'whatever',它不会抱怨。
不,它确实强制执行密钥类型。即使 myObject 有一个很好的 toString() 实现,你也不能做 stuff[myObject] = 'whatever'。
@RyanCavanaugh 是否可以(或将)使用 type Keys = 'Acceptable' | 'String' | 'Keys' 作为索引(键)类型?
小心使用 { number: string },因为即使这可能会在分配时强制执行索引的类型,但该对象仍然在内部将键存储为 string。这实际上会混淆 TypeScript 并破坏类型安全。例如,如果您尝试通过将键与值交换来将 { number: string } 转换为 { string: number },您实际上会得到 { string: string },但 TypeScript 不会引发任何警告/错误
S
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'.
};

这里,接口 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 的回答使它们完全无关紧要。如果您仍想使用它们,请查看编辑历史记录。


{ [键:数字]:T; } 不是类型安全的,因为对象的键在内部是 always 字符串——有关更多详细信息,请参阅@tep 对问题的评论。例如,在 Chrome 中运行 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'
@robocat这是真的,我已经来回编辑了一段时间,以从这个答案中删除数字键控接口。最终我决定保留它们,因为 TypeScript 在技术上特别允许数字作为键。话虽如此,我希望任何决定使用数字索引对象的人都能看到您的评论。
公平地说,这可以像这样改进吗:{ [name: string]: [age: number] } 包括数字值是年龄的提示? @桑迪吉福德
@Fasani 不幸的是,您刚刚定义的类型将是一个带有字符串作为键的对象,以及一个带有单个数字作为值的元组。但是,您可以使用该语法来提示元组中的值的用途!
@Fasani 看这里:typescriptlang.org/play?#code/…
d
dracstaxi

快速更新:从 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>


这应该得到更多的支持 - 它本质上是我答案的更新的本地版本。
记录会编译成 {} 吗?
@DouglasGaskell 类型在编译代码中没有任何存在。 Record(与 Javascript Map 不同)仅提供一种强制对象字面量内容的方法。您不能 new Record... 并且 const blah: Record<string, string>; 将编译为 const blah;
你甚至无法想象这个答案对我有多大帮助,非常感谢:)
值得一提的是,字符串联合也适用于 Recordconst isBroken: Record<"hat" | "teapot" | "cup", boolean> = { hat: false, cup: false, teapot: true };
A
AmerllicA

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

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

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

let stuff: StuffBody = {};

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

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

I
Ivan Pesochenko

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

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

L
Lasithds

定义接口

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

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

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

s
shabunc

@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")。
我个人喜欢本机打字稿格式,但最好使用标准。它为我提供了一种记录密钥“名称”的方法,该密钥实际上并不可用,但我可以将密钥命名为“patientId”之类的东西:)
这个答案是绝对有效的,并且提出了很好的观点,但我不同意坚持使用原生 Map 对象几乎总是更好的想法。地图会带来额外的内存开销,并且(更重要的是)需要从存储为 JSON 字符串的任何数据手动实例化。它们通常非常有用,但不仅仅是为了从中获取类型。
R
Roy Art

基于@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 定义。


x
x-magix
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 吗?
A
Ahmet Emrebas
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!');