如何确保打字稿中的字符串对象是通用的

时间:2019-04-22 17:14:34

标签: typescript typescript-generics

具有一个采用字符串索引对象的通用函数

function wantsStringIndexedObject<
    Obj extends { [k: string]: any }, 
    K extends keyof Obj
  >(obj: Obj, key: K) {

  const _ktype = typeof key
  // const ktype: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"

  const val = obj[key]

  wantsStringAndAny(key, val)
  // Argument of type 'K' is not assignable to parameter of type 'string'.
  // Type 'keyof Obj' is not assignable to type 'string'.
  //    Type 'string | number | symbol' is not assignable to type 'string'.
  //    Type 'number' is not assignable to type 'string'.
  // (parameter) key: K extends keyof Obj
} 

// no error here
wantsStringIndexedObject({
  1: 10,
  [Symbol()]:1
}, 1) 

function wantsStringAndAny(str: string, v:any) {} 

TS playground

我知道我可以在调用key之前在wantsStringAndAny上使用类型保护,但是我希望:
1:在key中肯定要将wantsStringIndexedObject输入为字符串
2:不应允许使用这种对象调用wantsStringIndexedObject

我想这个问题来自通用定义Obj extends { [k: string]: any }
实际上,使用任何索引类型的任何对象都会扩展具有0个属性的{ [k: string]: any }

那么,有没有一种方法可以定义确保字符串索引对象约束的泛型?

1 个答案:

答案 0 :(得分:2)

通过使用Extract条件类型来过滤Obj的键,可以确保键是字符串类型:

function wantsStringAndAny(str: string, v:any) {

} 

function wantsStringIndexedObject<
    Obj extends object, 
    K extends Extract<keyof Obj, string>
  >(obj: Obj, key: K) {

  const _ktype = typeof key
  const val = obj[key]

  wantsStringAndAny(key, val)
} 

// err
wantsStringIndexedObject({
  1: 10,
  [Symbol()]:1
}, 1)

//ok
wantsStringIndexedObject({
  "1": "",
}, "2")

通常,约束是类型必须实现的最小协定,没有什么可以阻止实际类型在其键中也没有符号。此外,字符串索引实际上将允许同时使用字符串和数字建立索引,因此在您的假设下也可以使用数字。

如果您还想禁止Obj中的任何数字或符号键,则可以执行以下操作:

function wantsStringIndexedObject<
    Obj extends object, 
    K extends Extract<keyof Obj, string>
  >(obj: Obj & Record<Extract<keyof Obj, number | symbol>, never>, key: K) {

  const _ktype = typeof key
  const val = obj[key]

  wantsStringAndAny(key, val)
} 

//ok
wantsStringIndexedObject({
  "1": "",
}, "1")
wantsStringIndexedObject({
  "1": "",
  2: 0 // errr
}, "1")

这需要任何numbersymbol键,并且基本上说它们应该是never类型(可能,但不太可能),并且任何这样的键都会出错。