基于鉴别器从类型联合中提取类型
Extracting type from union of types based on a discriminator
在下面的示例中,如何为 withoutSwitchReducer
中的 action
参数提供正确的类型?
enum ActionTypesEnum {
FOO = 'FOO',
BAR = 'BAR',
}
type ActionTypes = {
type: ActionTypesEnum.FOO,
payload: { username: string }
} | {
type: ActionTypesEnum.BAR,
payload: { password: string },
};
// "withSwitchReducer" works fine as TS can infer the descriminator from action.type
function withSwitchReducer(action: ActionTypes) {
switch (action.type) {
case 'FOO':
return action.payload.username;
case 'BAR':
return action.payload.password;
default:
return null;
}
}
// The code below gives as error as "action.payload.username" is not available on "action.payload.password" and vice versa
const withoutSwitchReducer = {
[ActionTypesEnum.FOO]: (action: ActionTypes) => {
return action.payload.username;
},
[ActionTypesEnum.BAR]: (action: ActionTypes) => {
return action.payload.password;
}
};
此处使用 Intellisense 的相同代码:TS Playground Link
有两种方法可以做到这一点。
您可以声明一次类型:
const withoutSwitchReducer: { [k in ActionTypesEnum]: (action: Extract<ActionTypes, { type: k }>) => string } = {
[ActionTypesEnum.FOO]: (action) => {
return action.payload.username;
},
[ActionTypesEnum.BAR]: (action) => {
return action.payload.password;
},
};
或者您可以单独描述它们:
const withoutSwitchReducer2 = {
[ActionTypesEnum.FOO]: (action: Extract<ActionTypes, { type: ActionTypesEnum.FOO }>) => {
return action.payload.username;
},
[ActionTypesEnum.BAR]: (action: Extract<ActionTypes, { type: ActionTypesEnum.BAR }>) => {
return action.payload.password;
},
};
声明一次类型的好处显然是您不必一遍又一遍地做同样的事情,但是单独描述它们可以让您从推断这些函数的 return 类型中获益。
更新:正如 Titian Cernicova-Dragomir 在评论中提到的,您可以将其声明为可在其他地方重用的类型:
type ReducerMap<T extends { type: string }> = { [P in T['type']]: (action: Extract<T, { type: P }>) => any }
函数的return类型是any
。
我试图找到一种方法来推断每个定义的实际 return 值,但这不太可能。
并且由于它用作缩减器映射,您可能不会关心 return 值,因为它已被框架使用。
ActionTypes
是复合类型。实例化变量时,应通过as
.
显式指明其具体类型
解决方案:
function withSwitchReducer(action: ActionTypes) {
switch (action.type) {
case 'FOO':
return (action.payload as {
type: ActionTypesEnum.FOO,
payload: { username: string }
}).username;
case 'BAR':
return (action.payload as {
type: ActionTypesEnum.BAR,
payload: { password: string }
}).password;
default:
return null;
}
}
const withoutSwitchReducer = {
[ActionTypesEnum.FOO]: (action: ActionTypes) => {
return (action.payload as {
type: ActionTypesEnum.FOO,
payload: { username: string }
}).username;
},
[ActionTypesEnum.BAR]: (action: ActionTypes) => {
return (action.payload as {
type: ActionTypesEnum.BAR,
payload: { password: string }
}).password;
}
};
更好的解决方案:
interface Foo {
type: 'FOO'
payload: { username: string }
}
interface Bar {
type: 'BAR'
payload: { password: string }
}
type ActionTypes = Foo | Bar
function withSwitchReducer(action: ActionTypes) {
switch (action.type) {
case 'FOO':
return (action.payload as Foo).username;
case 'BAR':
return (action.payload as Bar).password;
default:
return null;
}
}
const withoutSwitchReducer = {
'FOO': (action: ActionTypes) => {
return (action.payload as Foo).username;
},
'BAR': (action: ActionTypes) => {
return (action.payload as Bar).password;
}
};
字符串字面量可以作为类型,如var a: 'Apple'
。并且可以组合起来,比如var b: 'Apple' | 'Orange'
。
在下面的示例中,如何为 withoutSwitchReducer
中的 action
参数提供正确的类型?
enum ActionTypesEnum {
FOO = 'FOO',
BAR = 'BAR',
}
type ActionTypes = {
type: ActionTypesEnum.FOO,
payload: { username: string }
} | {
type: ActionTypesEnum.BAR,
payload: { password: string },
};
// "withSwitchReducer" works fine as TS can infer the descriminator from action.type
function withSwitchReducer(action: ActionTypes) {
switch (action.type) {
case 'FOO':
return action.payload.username;
case 'BAR':
return action.payload.password;
default:
return null;
}
}
// The code below gives as error as "action.payload.username" is not available on "action.payload.password" and vice versa
const withoutSwitchReducer = {
[ActionTypesEnum.FOO]: (action: ActionTypes) => {
return action.payload.username;
},
[ActionTypesEnum.BAR]: (action: ActionTypes) => {
return action.payload.password;
}
};
此处使用 Intellisense 的相同代码:TS Playground Link
有两种方法可以做到这一点。
您可以声明一次类型:
const withoutSwitchReducer: { [k in ActionTypesEnum]: (action: Extract<ActionTypes, { type: k }>) => string } = {
[ActionTypesEnum.FOO]: (action) => {
return action.payload.username;
},
[ActionTypesEnum.BAR]: (action) => {
return action.payload.password;
},
};
或者您可以单独描述它们:
const withoutSwitchReducer2 = {
[ActionTypesEnum.FOO]: (action: Extract<ActionTypes, { type: ActionTypesEnum.FOO }>) => {
return action.payload.username;
},
[ActionTypesEnum.BAR]: (action: Extract<ActionTypes, { type: ActionTypesEnum.BAR }>) => {
return action.payload.password;
},
};
声明一次类型的好处显然是您不必一遍又一遍地做同样的事情,但是单独描述它们可以让您从推断这些函数的 return 类型中获益。
更新:正如 Titian Cernicova-Dragomir 在评论中提到的,您可以将其声明为可在其他地方重用的类型:
type ReducerMap<T extends { type: string }> = { [P in T['type']]: (action: Extract<T, { type: P }>) => any }
函数的return类型是any
。
我试图找到一种方法来推断每个定义的实际 return 值,但这不太可能。
并且由于它用作缩减器映射,您可能不会关心 return 值,因为它已被框架使用。
ActionTypes
是复合类型。实例化变量时,应通过as
.
解决方案:
function withSwitchReducer(action: ActionTypes) {
switch (action.type) {
case 'FOO':
return (action.payload as {
type: ActionTypesEnum.FOO,
payload: { username: string }
}).username;
case 'BAR':
return (action.payload as {
type: ActionTypesEnum.BAR,
payload: { password: string }
}).password;
default:
return null;
}
}
const withoutSwitchReducer = {
[ActionTypesEnum.FOO]: (action: ActionTypes) => {
return (action.payload as {
type: ActionTypesEnum.FOO,
payload: { username: string }
}).username;
},
[ActionTypesEnum.BAR]: (action: ActionTypes) => {
return (action.payload as {
type: ActionTypesEnum.BAR,
payload: { password: string }
}).password;
}
};
更好的解决方案:
interface Foo {
type: 'FOO'
payload: { username: string }
}
interface Bar {
type: 'BAR'
payload: { password: string }
}
type ActionTypes = Foo | Bar
function withSwitchReducer(action: ActionTypes) {
switch (action.type) {
case 'FOO':
return (action.payload as Foo).username;
case 'BAR':
return (action.payload as Bar).password;
default:
return null;
}
}
const withoutSwitchReducer = {
'FOO': (action: ActionTypes) => {
return (action.payload as Foo).username;
},
'BAR': (action: ActionTypes) => {
return (action.payload as Bar).password;
}
};
字符串字面量可以作为类型,如var a: 'Apple'
。并且可以组合起来,比如var b: 'Apple' | 'Orange'
。