Typescript: ограничить аргумент функции, чтобы быть ключом объекта, связанного со значением определенного типа - PullRequest
0 голосов
/ 05 сентября 2018

Есть ли способ сделать следующую проверку типа?

function getNumberFromObject<T>(obj: T, key: keyof T): number {
  return obj[key] // ERROR: obj[key] might not be a number
}

Я хочу указать, что key должен быть не только ключом T, но и ключом со значением number.

1 Ответ

0 голосов
/ 05 сентября 2018

Самый простой способ сделать это так, чтобы как вызывающие, так и реализация проверки типа getNumberFromObject<T> правильно выглядели так:

function getNumberFromObject<T extends Record<K, number>, K extends keyof any>(
  obj: T, 
  key: K
): number {
  return obj[key] // okay
}

И когда ты это называешь:

getNumberFromObject({dog: 2, cat: "hey", moose: 24}, "dog"); // okay
getNumberFromObject({dog: 2, cat: "hey", moose: 24}, "cat"); // error
getNumberFromObject({dog: 2, cat: "hey", moose: 24}, "moose"); // okay
getNumberFromObject({dog: 2, cat: "hey", moose: 24}, "squirrel"); // error

Это все работает хорошо, за исключением того, что ошибки, которые вы получаете, немного неясны в том, что они жалуются, что Object literal may only specify known properties, and 'dog' does not exist in type 'Record<"somebadkey", number>'. Эта жалоба является проверкой избыточности и не является проблемой.

Если вы хотите, чтобы вызывающие абоненты получали более точную ошибку, вы можете использовать более сложный условный тип , например:

function getNumberFromObject<T, K extends keyof any & {
  [K in keyof T]: T[K] extends number ? K : never
}[keyof T]>(
  obj: T,
  key: K
): T[K] {
  return obj[key] // okay
}

getNumberFromObject({ dog: 2, cat: "hey", moose: 24 }, "dog"); // okay
getNumberFromObject({ dog: 2, cat: "hey", moose: 24 }, "cat"); // error
getNumberFromObject({ dog: 2, cat: "hey", moose: 24 }, "moose"); // okay
getNumberFromObject({ dog: 2, cat: "hey", moose: 24 }, "squirrel"); // error

В этом случае T является неограниченным, но K вынужден быть только теми ключами из T, где T[K] - это number.

Теперь об ошибке написано Argument of type '"somebadkey"' is not assignable to parameter of type '"dog" | "moose"'., что более удобно для разработчиков. Не уверен, стоит ли вам дополнительная сложность подписи.

Надеюсь, это поможет. Удачи!


Обновление: последняя функция возвращает T[K], а не number. Это может быть хорошо, поскольку T[K], возможно, более конкретно, чем number. Например:

interface Car {
  make: string,
  model: string,
  horsepower: number,
  wheels: 4
}
declare const car: Car;
const four = getNumberFromObject(car, 'wheels'); // 4, not number

Значение four имеет тип 4, который является более конкретным, чем number. Если вы действительно хотите расширить тип возвращаемого значения функции до number, вы можете ... хотя реализация будет препятствовать этому, поскольку компилятор не достаточно умен, чтобы понять, что T[K] можно назначить number в общий случай. Есть способы справиться с этим, но проще всего использовать утверждение типа в реализации (return obj[key] as any as number).

...