Самый простой способ сделать это так, чтобы как вызывающие, так и реализация проверки типа 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
).