繁体   English   中英

打字稿:映射类型中的索引签名

[英]Typescript: index signatures in mapped type

我怎样才能使用类型{ 'k': number, [s: string]: any }并抽象'k'number 我想要一个类型别名T使得T<'k', number>给出所述类型。


考虑以下示例:

function f(x: { 'k': number, [s: string]: any }) {}                           // ok
type T_no_params = { 'k': number, [s: string]: any };                         // ok
type T_key_only<k extends string> = { [a in k]: number };                     // ok
type T_value_only<V> = { 'k': V, [s: string]: any};                           // ok
type T_key_and_index<k extends string, V> = { [a in k]: V, [s: string]: any };// ?
  • 直接使用{ 'k': number, [s: string]: any}作为函数f的参数类型。
  • 使用[s: string]: any type -alias 中的[s: string]: any索引部分都有效
  • type -alias 中使用k extends string也有效
  • 当将k extends string[s: string]: any in same type -alias 组合时,我得到一个解析错误(甚至不是语义错误,它甚至似乎不是有效的语法)。

这在这里似乎有效:

type HasKeyValue<K extends string, V> = { [s: string]: any } & { [S in K]: V }

但在这里,我不太明白为什么它不抱怨额外的属性( &右侧的类型不应该允许具有额外属性的对象)。


编辑

在答案中多次提到&是交集运算符,它的行为应该类似于集合论交集。 然而,当涉及到额外属性的处理时,情况并非如此,如以下示例所示:

function f(x: {a: number}){};
function g(y: {b: number}){};
function h(z: {a: number} & {b: number}){};

f({a: 42, b: 58});  // does not compile. {a: 42, b: 58} is not of type {a: number}
g({a: 42, b: 58});  // does not compile. {a: 42, b: 58} is not of type {b: number}
h({a: 42, b: 58});  // compiles!

在这个例子中,似乎{a: 42, b: 58}既不是{a: number}类型,也不是{b: number}类型,但它以某种方式结束在交集{a: number} & {b: number} 这不是集合论交集的工作原理。

这正是我自己的& -proposal 看起来如此可疑的原因。 如果有人能详细说明如何将映射类型与{ [s: string]: any } “相交”可以使类型“更大”而不是使其更小,我将不胜感激。


我看过问题

但这些似乎没有直接关系,尽管名称相似。

type HasKeyValue<K extends string, V> = { [s: string]: any } & { [S in K]: V }是定义您所追求的类型的正确方法。 但要知道的一件事是(解释不推荐使用的标志:keyofStringsOnly ):

keyof 类型运算符返回字符串 | 当应用于具有字符串索引签名的类型时,数字而不是字符串。

我不知道将索引限制为string类型而不是string | number string | number 实际上允许number访问string索引似乎是一件合理的事情,因为它符合 Javascript 的工作方式(人们总是可以对数字进行字符串化)。 另一方面,您不能安全地访问带有字符串值的数字索引。


&类型运算符的工作方式类似于设置理论交集 - 它总是限制可能值的集合(或保持它们不变,但从不扩展)。 在您的情况下,该类型将任何非字符串键作为索引排除在外。 准确地说,您将unique symbol排除在索引之外。

我认为您的困惑可能来自 Typescript 处理函数参数的方式。 使用明确定义的参数调用函数与将参数作为变量传递的行为不同。 在这两种情况下,Typescript 都确保所有参数都具有正确的结构/形状,但在后一种情况下,它另外不允许额外的道具。


说明概念的代码:

type HasKeyValue<K extends string, V> = { [s: string]: any } & { [S in K]: V };
type WithNumber = HasKeyValue<"n", number>;
const x: WithNumber = {
  n: 1
};

type T = keyof typeof x; // string | number
x[0] = 2; // ok - number is a string-like index
const s = Symbol("s");
x[s] = "2"; // error: cannot access via symbol

interface N {
  n: number;
}

function fn(p: N) {
  return p.n;
}

const p1 = {
  n: 1
};

const p2 = {
  n: 2,
  s: "2"
};

fn(p1); // ok - exact match
fn(p2); // ok - structural matching: { n: number } present;  additional props ignored
fn({ n: 0, s: "s" }); // error: additional props not ignore when called explictily
fn({}); // error: n is missing

编辑

对象字面量 - 显式创建某种形状的对象,如const p: { a: number} = { a: 42 }被 Typescript 以特殊方式处理。 与常规结构推理相反,类型必须完全匹配。 老实说这是有道理的,因为这些额外的属性 - 没有额外的可能不安全的演员 - 无论如何都无法访问。

[...] 但是,TypeScript 认为此代码中可能存在错误。 对象文字在将它们分配给其他变量或将它们作为参数传递时会得到特殊处理并进行额外的属性检查。 如果对象字面量具有“目标类型”没有的任何属性,您将收到错误消息。 [...] 绕过这些检查的最后一种方法(可能有点令人惊讶)是将对象分配给另一个变量。

TS手册

解决此错误的另一个选择是...将其与{ [prop: string]: any }相交。

更多代码:

function f(x: { a: number }) {}
function g(y: { b: number }) {}
function h(z: { a: number } & { b: number }) {}

f({ a: 42, b: 58 } as { a: number }); // compiles - cast possible, but `b` inaccessible anyway
g({ a: 42 } as { b: number }); // does not compile - incorrect cast; Conversion of type '{ a: number; }' to type '{ b: number; }' may be a mistake
h({ a: 42, b: 58 }); // compiles!

const p = {
  a: 42,
  b: 58
};

f(p); // compiles - regular structural typing
g(p); // compiles - regular structural typing
h(p); // compiles - regular structural typing

const i: { a: number } = { a: 42, b: 58 }; // error: not exact match
f(i); // compiles
g(i); // error
h(i); // error

这是有关交集运算符的一种推理方式。 也许它有帮助:

type Intersection = { a: string } & { b: number }

你可以阅读Intersection为“具有属性的对象a类型的string属性b的类型number ”。 这恰好也描述了这种简单的类型:

type Simple = { a: string; b: number }

并且这两种类型是兼容的。 对于几乎所有用途,您都可以将其中一个替换为另一个。

我希望这可以解释为什么HasKeyValue确实与您尝试定义的类型相同。

至于为什么T_key_and_index不起作用,是因为第一部分[a in k]: V定义了映射类型,并且在映射类型的定义中不能有额外的属性。 如果需要向映射类型添加额外的属性,可以使用&创建类型交集

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM