Reputation: 148514
I have a generic method which is from the RXJS library (here simplified) :
function Subject<T>(t: T):T {
return t;
}
I also have an interface which declares my app values. (keys can be added)
interface IState {
key1: number;
key2: string;
}
And finally I have a Store
which applies to the IState
interface with actual values, via the generic function wrapper ( the generic function is an alias to the RXJS
subject)
let Store : IState= Subject<IState>({
key1: void 0,
key2: void 0,
})
Ok so lets add 2 method for getting & setting from store :
Setting to store :
function set<T>(name: keyof IState, statePart: T) {
Store={
...Store,
[name]: statePart
};
Usage : set<string>("key1", "3");
This function works fine and allows me to only use valid keys which belongs to IState
. No errors here.
But looking at the Select
method :
(invocation should be like: )
let myVal:number = select<number>("key1");
Here is the method :
function select<T>(k: keyof IState): T {
return <T>Store[k]; // <-- error here
}
Type 'string | number' cannot be converted to type 'T'. Type 'number' is not comparable to type 'T'.
Question:
Why is that ? If I remove the keyof
:
function select<T>(k): T {
return <T>Store[k];
}
Then it does compile , but it's not making any sense , Store
is type of Istate
and Istate
contains keys of Istate
Why does it work without keyof
and how can I fix my code so that select
method will force to select only keys of Istate
?
Upvotes: 3
Views: 177
Reputation: 249466
The problem is that k
can be any key of IState
so there is no way to make sure that Store[k]
is compatible with T
. I would change the generic parameter to be the key, and type the result in relation to the field type:
function select<K extends keyof IState>(k: K): IState[K] {
return Store[k];
}
let myVal = select("key1"); // myval is number
Also set
can be improved to not have explicit generic parameters, and to ensure the value passed to set
is compatible with the field type:
function set<K extends keyof IState>(name: K, statePart: IState[K]) {
Store={
...Store,
[name]: statePart
}
}
set("key1", 3);
Edit
As mentioned in the comments, you can see the actual inferred types if you hover over the call (at least in vscode):
If you want to keep the explicit type parameters, although I do not recommend this as you can easily mismatch the field type and the call type you can use a type assertion through any
:
function select3<T>(k: keyof IState): T {
return Store[k] as any;
}
Upvotes: 3