TS не может вычислить результат BoolPropNames<T>
, так как он вычисляется, когда задано T
, иначе, чем в предыдущих примерах, где у вас не было переменной типа, но был какой-то определенный тип c. Например, BoolPropNames<Thing>
вычисляется в isGreat
, а thing[key]
известно как boolean
.
BoolPropNames<T>
не может быть вычислено раньше, поэтому он не знает, что результатом будет ключ, который дает нам boolean
. И если он не знает, что think[key]
является логическим значением, вы не можете присвоить ему логическое значение.
Чтобы это исправить, мы можем, например, применить аргумент, который будет рассматриваться как тип T[K]
. Тогда TS не нужно вычислять тип, поскольку он всегда будет значением типа для данного ключа. Рассмотрим следующий код:
function assign<T, K extends BoolPropNames<T>>(thing: T, key: K, value: T[K]) {
thing[key] = value;
}
assign({ a: true }, 'a', false);
Если третий аргумент вас не удовлетворяет, и вы просто хотите установить любое значение без указания его в качестве аргумента, мы также можем сделать некоторое частичное применение для создания такого поведения , Рассмотрим:
// function which creates assign function
const createAssign = <T, K extends BoolPropNames<T> = BoolPropNames<T>>
(value: T[K]) => (thing: T, key: K) => {
thing[key] = value;
}
// below assign function is created for type `Example`
type Example = { a: boolean };
const assign = createAssign<Example>(false);
assign({a: true}, 'a');
При частичном применении мы создали функцию assign
, которая работает так же, как и ваша оригинальная, поэтому она присваивает false
только полям boolean
. Таким способом вы можете создавать все такие функции.
Конечно, недостатком является то, что нам нужно генерировать такие для каждого типа, устанавливая переменную типа generi c, так что это не так полиморфно c как решение с другим порядком аргументов.