class Selector<Interface> {
public where<Key extends keyof Interface>(
key: keyof Pick<Interface, Key>,
value: Interface[Key]
) {
// [implementation]
}
}
// Multiple types to check the Pick
interface IBook {
author: string;
pagesCount: number;
wasRead: boolean;
}
const selector = new Selector<IBook>();
Key
(T) will dynamically create a generic for Pick
from keys of Interface
(U) and the type of value
argument will be inferred from the key
we pass to the method.
// notice:
key: keyof Pick<Interface, Key>
We specifically have to narrow it down to just one key of U
, which will effectively infer a single type (later in value
argument), not all types from the interface if we leave just the key: keyof U
.
Consider the other workarounds found somewhere on the Internet:
public test(key: keyof T, value: T[keyof T]) {}
or even
public test<T extends keyof U>(key: keyof U, value: U[keyof T]) {}
This gives no type safety since it accepts all types from U
that can be unrelated to key
, which is not what we want.
This can also work on generic functions:
function where<Key extends keyof IBook>(key: keyof Pick<IBook, Key>, value: IBook[Key]) {}
where("pagesCount", "2"); // Fails
where("pagesCount", 2); // Passes
where("pagesCount", false); // Fails
I don't know how to make such function 100% generic, though. It could be something like this:
function where<U, Key extends keyof U>(key: keyof Pick<U, Key>, value: U[Key]) {}
but then it would require such horrible syntax:
where<IBook, "author">("author", 1); // Fails, which is good
PS: I figured this out completely by accident, I don't know much about TypeScript