I'm trying to pass a string index to use on one of two state types. It throws the following error:
type Index = 'name' | 'length' | 'width' | 'depth' | 'height'
interface StateOne {
name: string
length: number
}
interface StateTwo {
width: number
depth: number
height: number
}
interface SwitchProps {
state: StateOne | StateTwo
index: Index
}
function testFunction({state, index}: SwitchProps) {
const test = state[index]
}
//TS7053: Element implicitly has an 'any' type because expression of type 'Index' can't be used to index type 'StateOne | StateTwo'.
//Property 'name' does not exist on type 'StateOne | StateTwo'.
How would you write this to get around the problem?
This works, but it is a bad workaround:
const test = state[index as unknown as keyof typeof state]
CodePudding user response:
You can use generics and restrict index
to be a valid key of state
:
function testFunction<K extends keyof T, T>({ state, index }: { state: T, index: K }) {
return state[index]
}
declare const state1: StateOne;
declare const state2: StateTwo;
testFunction({ state: state1, index: 'name' }) // string
testFunction({ state: state2, index: 'width' }) // number
// Error: Type '"width"' is not assignable to type 'keyof StateOne'
testFunction({ state: state1, index: 'width' })
CodePudding user response:
You can use a type predicate to discriminate between your types:
interface StateOne {
name: string;
length: number;
}
interface StateTwo extends Record<'depth' | 'height' | 'width', number> {}
type PropVariant<T> = {
state: T;
index: keyof T;
};
type SwitchProps = PropVariant<StateOne> | PropVariant<StateTwo>;
function isStateOne (props: SwitchProps): props is PropVariant<StateOne> {
/** `"name"` only exists in SwitchOne, so if this returns `true`, we know it's that type */
return 'name' in props.state;
}
function testFunction (props: SwitchProps): void {
if (isStateOne(props)) {
const value = props.state[props.index]; // string | number
console.log(value);
const {length} = props.state; // string
}
else {
const value = props.state[props.index]; // number
console.log(value);
const {depth} = props.state; // number
}
}