【问题标题】:How to dynamically assign properties to an object in TypeScript by using Record correctly?如何正确使用 Record 为 TypeScript 中的对象动态分配属性?
【发布时间】:2021-09-29 21:12:19
【问题描述】:

我尝试定义一种可以分配对象中任何键的类型。然后,我发现this 是一个很好的答案。

type AnyMap = Record<string, any>;
const obj: AnyMap = {};
const obj1: AnyMap = {
  a: 'a',
  b: 'b'
}

但是,我发现AnyMap 也可以分配给一个数组并且它没有显示任何错误。

const arr1: AnyMap = ['a', 'b'];

我只想AnyMap 只定义object的类型,不包括array。所以,我尝试创建另一种类型。

type UnknownMap = Record<string, unknown>;

// obj2 is works fine
const obj2: UnknownMap = {
    a: 'a',
    b: 'b'
}

// obj3 is works fine
const obj3: UnknownMap = {
    0: 'a',
    1: 'b'
}

// arr2 throws an error
// Type 'string[]' is not assignable to type 'UnknownMap'. Index signature is missing in type 'string[]'.(2322)
const arr2: UnknownMap = ['a', 'b'];

UnknownMap 的结果对我来说似乎不错。它只定义了 object 的类型,并抛出 array 的错误。最后,我有两个问题我不太明白。

  1. 如果我只想定义object的类型,不想包含array的类型。使用UnknownMap 而不是AnyMap 更好吗?

  2. 为什么 const arr2: UnknownMap = ['a', 'b'] 会抛出错误 Type 'string[]' is not assignable to type 'UnknownMap'. Index signature is missing in type 'string[]'.(2322) ?我不知道为什么仅仅因为我在Record 中将any 替换为unknown 就会引发错误。另外,我不知道为什么obj3 可以正常工作,但arr2 会抛出错误。

TypeScript Playground

我希望上面的链接可以帮助很好地解释我的问题。如果有任何描述让您感到困惑,请告诉我,谢谢。

【问题讨论】:

  • 但是数组对象吗?
  • (2) 确实很奇怪。
  • 我也不太确定,我对答案很感兴趣。我认为这与类型推断有关。因为这有效const arr2: UnknownMap = (['a', 'b'] as unknown) as UnknownMap; 抱歉似乎无法插入链接
  • FWIW 这似乎是特定于Array,一种与Array 具有相同行为的自写类型在这种情况下的行为不同。 demo

标签: typescript


【解决方案1】:

第 1 部分

为什么 const arr2: UnknownMap = ['a', 'b'] 会抛出错误 Type 'string[]' is not assignable to type 'UnknownMap'。 'string[]'.(2322) 类型中缺少索引签名?我不知道为什么它会抛出一个错误,只是因为我在 Record 中用 unknown 替换了 any。另外,我不知道为什么 obj3 可以正常工作,但 arr2 会抛出错误。

为了更好的理解这个错误,请在TS playground切换到TypeScript 4.4.0版本。

此后,错误消息得到了改进。

我相信这个错误(来自 TS 4.3.*): Type 'string[]' is not assignable to type 'UnknownMap'. Index signature is missing in type 'string[]' 不正确,因为 string[] 具有索引签名。

lib.es5.d.ts。这是数组类型声明的一部分:

// lib.es5.d.ts
interface Array<T> {
    [n: number]: T
}

如果你在 TS playground 切换到 TS 4.4.0 - beta,你会看到这个错误:

Type 'string[]' is not assignable to type 'UnknownMap'.
  Index signature for type 'string' is missing in type 'string[]'

上面的错误信息是有道理的。

为了更好地理解 TS 错误信息,考虑下一个例子:

declare var arr: { [prop: number]: string };
declare var rec: { [prop: string]: unknown };

arr = rec; // error
rec = arr // ok

错误信息:

Type '{ [prop: string]: unknown; }' is not assignable to type '{ [prop: number]: string; }'.
  'string' and 'number' index signatures are incompatible.
    Type 'unknown' is not assignable to type 'string'.(2322)

因此,我们可以将问题简化为:

declare var str: string;
declare var unk: unknown;

str = unk;
unk = str

所以,这里的主要问题是,Type 'unknown' is not assignable to type 'string'any 是否可分配给类型 string

让我们回到我们的主要错误信息:

type AnyMap = Record<string, any>;
const obj1: AnyMap = {
    a: 'a',
    b: 'b'
}
const arr1: AnyMap = ['a', 'b'];

type UnknownMap = Record<string, unknown>;

const obj2: UnknownMap = {
    a: 'a',
    b: 'b'
}
const obj3: UnknownMap = {
    0: 'a',
    1: 'b'
}

const arr2: UnknownMap =  ['a', 'b']; // error

消息:

Type 'string[]' is not assignable to type 'UnknownMap'.
  Index signature for type 'string' is missing in type 'string[]'

因为Record&lt;string, unknown&gt; 只是一个别名

{
    [x: string]: unknown;
}

['a', 'b'] 推断为string[],或者换句话说

{
    [x: number]: string;
}

[x: number]: string; 的索引签名不能分配给[x: string]: unknown,因为Type 'unknown' is not assignable to type 'string'

请记住,x:string 作为索引签名本身可以分配给 x:number,反之亦然,因为根据 JS 规范,所有键都被评估为 string

第 2 部分

如果我只想定义对象的类型并且不想包含数组的类型。使用 UnknownMap 而不是 AnyMap 更好吗?

为了区分字典和数组,你需要从某个地方推断出这个类型。您可以创建一个实用程序类型,以便从函数参数中推断它。请记住,TypeScript 中没有 negation 运算符。

考虑这个例子:

type Primitives =
    | string
    | number
    | symbol
    | boolean
    | undefined
    | bigint
    | null // not sure how you want to treat this one :D

type AnObject<T = any> =
    T extends Primitives | Array<any> ? never : T
{

    type Test = AnObject<Record<number, 2>> // ok
    type Test2 = AnObject<[]> // never
    type Test3 = AnObject<2> // never
}

const onlyObj = <T,>(obj: AnObject<T>) => { }
onlyObj([]) // Error
onlyObj(2) // Error
onlyObj({}) // ok

Here,在我的博客中,您可以找到更多关于 typescript 中类型否定的示例/信息。

【讨论】:

  • 在同一个playground 我添加了字符串而不是未知的错误仍然存​​在。对我来说似乎是错误。
  • 你怎么说@captain-yossarian?
  • @SubratoPatnaik 是的,这对我来说也很奇怪。我不知道怎么解释。
  • @captain-yossarian 感谢您的解释。很容易理解。
  • 嗨@captain-yossarian,我发现了一个奇怪的部分。对于Array的情况,应该是[x: number]: string赋值给[x: string]: unknown吧?但是,您的回答是[x: string]: unknown; is not assignable to [x: number]: string。看来你的回答让它颠倒了。
猜你喜欢
  • 2019-08-23
  • 2021-06-03
  • 2018-08-13
  • 2016-12-08
  • 1970-01-01
  • 2013-03-25
  • 2021-11-27
相关资源
最近更新 更多