我试图创建一个帮助程序通用actionCreator
函数,以避免每个操作的actionCreator类型函数:
interface Response {
readonly value: 'A' | 'B';
}
enum ActionTypes {
created = 'ns/created',
deleted = 'ns/deleted',
}
interface Created {
readonly type: ActionTypes.created;
readonly payload: Response;
}
interface Deleted {
readonly type: ActionTypes.deleted;
}
type Action = Created | Deleted;
// actionCreator = (type, payload) => ({type, payload});
// actionCreator(ActionTypes.created, response);
// actionCreator(ActionTypes.deleted);
是否可以告诉TS actionCreator
不应该只返回一个字符串和一个可选的有效负载对象,而是要知道type Action
来检查类型和有效负载对?
像function actionCreator<T>(type: T.type, payload: T.payload)
这样无效的TS。
答案 0 :(得分:1)
以下所有假设您不仅仅有两种类型的操作。如果你真的只有两个,那么使用函数重载并完成它:
function actionCreator(type: ActionTypes.created, payload: Response): Created;
function actionCreator(type: ActionTypes.deleted): Deleted;
function actionCreator(type: any, payload?: any) {
return (typeof payload === 'undefined') ? { type } : { type, payload }
}
有关更一般的内容,请继续阅读:
一旦conditional types功能登陆TypeScript 2.8(预计将于2018年3月某个时候发布,您现在可以使用typescript@next
进行尝试),您将能够指定一个行为为的功能(I想你想要的,而不是触及你现有的代码,如下:
// If K is a key of T, return T[K]. Otherwise, return R
type MaybePropType<T, K extends string, R=never> = K extends keyof T ? T[K] : R
// Given K, one of the "type" properties of the Action union,
// produce the constituent of Action union whose type is K. So:
// ActionFromType<ActionTypes.created> is Created, and
// ActionFromType<ActionTypes.deleted> is Deleted.
type ActionFromType<K extends Action['type'], AA = Action>
= AA extends infer A
? MaybePropType<AA, 'type'> extends K ? AA : never
: never
// The one-argument actionCreator() call accepts an element of ActionTypes
// corresponding to an Action with no payload, and produces an Action of that type.
function actionCreator<
T extends ActionTypes & ('payload' extends keyof ActionFromType<T> ? never : {})
>(type: T): ActionFromType<T>;
// The two-argument actionCreator() call accepts an element from ActionTypes
// corresponding to an Action with a payload, and a value of the payload type,
// and produces an Action of that type.
function actionCreator<
T extends ActionTypes & ('payload' extends keyof ActionFromType<T> ? {} : never)
>(type: T, payload: MaybePropType<ActionFromType<T>, 'payload'>): ActionFromType<T>;
// The implementation of actionCreator() creates a one-or-two-property
// value from its arguments. Note that this implementation won't be right
// if you ever add new Action types that require more properties
function actionCreator(type: any, payload?: any) {
return (typeof payload === 'undefined') ? { type } : { type, payload }
}
以下是它的工作原理:
declare const resp: Response;
// no errors
const created = actionCreator(ActionTypes.created, resp); // Created
const deleted = actionCreator(ActionTypes.deleted); // Deleted
// errors
const badCreated = actionCreator(ActionTypes.created); // missing payload
const badDeleted = actionCreator(ActionTypes.deleted, "payload?!"); // unexpected payload
const badDeleted2 = actionCreator('ns/deleted'); // not typed as an enum
这是很多类型杂耍,一些错误信息将是神秘的,它取决于一个非常新的功能。
如果你不介意改变你的代码,你可以得到一些适用于TypeScript 2.7的东西,actionCreator()
更容易推理...但是它之前的样板可能有点令人生畏:< / p>
// create a mapping from action type to payload type
type ActionPayloads = {
'ns/created': Response
}
// create a list of actions with no payloads
type ActionsWithoutPayloads = 'ns/deleted';
// this is a helper type whose keys are the ActionTypes
// and whose values represent the part of the Action without the type
// so, just {} for a payloadless type, and {readonly payload: P} for
// a payloaded type
type ActionMap = { [K in ActionsWithoutPayloads]: {} } &
{ [K in keyof ActionPayloads]: { readonly payload: ActionPayloads[K] } }
// this is a helper function which makes sure we define ActionTypes
// correctly
const keysOfActionMap = <T extends { [k: string]: keyof ActionMap }>(x: T) => x;
// now we have ActionTypes. It is a plain object, not an enum,
// but it behaves similarly
const ActionTypes = keysOfActionMap({
created: 'ns/created',
deleted: 'ns/deleted'
});
// some helper type functions
type IntersectionToObject<T> = { [K in keyof T]: T[K] }
type ValueOf<T> = T[keyof T];
// extract Action from previous stuff.
// Action by itself is a union of actions from before, while
// Action<K> is the action corresponding to K from ActionTypes.
type Action<K extends keyof ActionMap = keyof ActionMap> =
ValueOf<{
[P in K]: IntersectionToObject<{ readonly type: P } & ActionMap[P]>
}>
// if you need names for Created and Deleted:
type Created = Action<typeof ActionTypes.created>;
type Deleted = Action<typeof ActionTypes.deleted>;
最后这是函数,它的输入更简单:
// actions without payloads only accept one parameter
function actionCreator<K extends ActionsWithoutPayloads>(
type: K): Action<K>;
// actions with payloads require two parameters
function actionCreator<K extends keyof ActionPayloads>(
type: K, payload: ActionPayloads[K]): Action<K>;
// same impl as before
function actionCreator(type: any, payload?: any) {
return (typeof payload === 'undefined') ? { type } : { type, payload }
}
让我们确保它有效:
declare const resp: Response;
// no errors
const created = actionCreator(ActionTypes.created, resp); // Created
const deleted = actionCreator(ActionTypes.deleted); // Deleted
const okayDeleted = actionCreator('ns/deleted'); // okay this time
// errors
const badCreated = actionCreator(ActionTypes.created); // missing payload
const badDeleted = actionCreator(ActionTypes.deleted, "payload?!"); // unexpected payload
呼!希望有所帮助;祝你好运。