Typescript如何在扩展时强制记录键为字符串

xe55xuns  于 2023-04-07  发布在  TypeScript
关注(0)|答案(1)|浏览(118)

我正在尝试创建一个函数,它将接受一个对象作为map,并且一个进一步嵌套的函数将使用它的键(我将来也需要这个值):

function exec(a: string) {
    return a;
  }
  
  function one<T extends Record<string, any>>(a: T) {
    return <K extends keyof T>(b: K) => {
    exec(b);// TS2345: Argument of type 'string | number | symbol' is not assignable to parameter of type 'string'.   Type 'number' is not assignable to type 'string'.
   };  
 }

但我得到这个错误TS2345: Argument of type 'string | number | symbol' is not assignable to parameter of type 'string'.   Type 'number' is not assignable to type 'string'.,因为对象键可能是number|符号,而不是严格的字符串。可以不使用exec(b.toString())吗?

wribegjk

wribegjk1#

正如你所注意到的,字符串索引签名并不能阻止非string键的存在(这只意味着任何存在的string键都需要具有与索引签名兼容的属性,这对于Record<string, any>来说是非常宽松的)。即使U没有特定的属性键,这并不意味着T不能拥有该键。本质上,TypeScript中的对象类型是 open 而不是 exact(如microsoft/TypeScript#12936中所要求的),并且在大多数情况下,编译器不会禁止多余的属性。
这意味着你可以像这样调用one

const sym = Symbol();
const a = { str: 1, [sym]: 2 }
const fn = one(a); // okay
fn("str"); // okay
fn(sym); // okay

这就是为什么one的实现抱怨b可能不是string
如果你想防止这种情况,你需要将K约束为不仅仅是keyof T,而是keyof T的子类型,也可以分配给string。最简单的表达方法是将stringkeyof T相交:

function one<T extends Record<string, any>>(a: T) {
    return <K extends string & keyof T>(b: K) => {
        exec(b); // okay
    };
}

现在编译器知道b必须是keyof T以及string。现在如果你试图用非string输入调用从one返回的函数,你会得到想要的错误:

const sym = Symbol();
const a = { str: 1, [sym]: 2 }
const fn = one(a); // okay
fn("str"); // okay
fn(sym); // error

Playground链接到代码

相关问题