Issue
I have a usecase within my app where I want to be able to specify message structures using a simple TypeScript generic and have that accompanied by a simple message factory. I came up with the following:
export type Message<
T extends string,
P extends Record<string, any> = Record<string, never>
> = {
type: T;
payload: P;
};
export const msg = <M extends Message<string, Record<string, any>>>(
type: M['type'],
payload: M['payload']
): M => ({ type, payload });
type UserRegistered = Message<'UserRegistered', { name: string }>;
const message = <UserRegistered>msg('UserRegistered', { name: 'Test' });
But I'm getting a TS2322 error on the ): M => ({ type, payload });
line:
error TS2322: Type '{ type: M["type"]; payload: M["payload"]; }' is not assignable to type 'M'. '{ type: M["type"]; payload: M["payload"]; }' is assignable to the constraint of type 'M', but 'M' could be instantiated with a different subtype of constraint 'Message<string, Record<string, any>>'.
I'm struggling to figure out how this could be a type safety risk and why it doesn't transpile, since I'm essentially destructuring and reconstructing the same type.
(If you're questioning the usefulness of this thin factory layer, I'm banking on it helping maintainability with a large set of prebuilt named factory functions)
Solution
As a rule of thumb, try to infer function arguments from bottom to top.
I mean, dont try to infer <M extends Message<string, Record<string, any>>
whole M
generic type. First of all, infer each nested property of this type, then you can put it all togeter.
It is not necessary to use explicit return type, at least in your case. Keep in mind, TS has structural type system, not nominal.
Avoid using explicit generic when you call your function. 99% is is not necessary. TS should infer all types for you
export type Message<
T extends string,
P extends Record<string, any> = Record<string, never>
> = {
type: T;
payload: P;
};
export const msg = <
Type extends string,
Payload extends Record<string, any> = Record<string, never>
>(
type: Type,
payload: Payload
): Message<Type, Payload> => ({ type, payload })
type UserRegistered = Message<'UserRegistered', { name: string }>;
const message = msg('UserRegistered', { name: 'Test' })
/**
* UserRegistered is redundant here
*/
const message_: UserRegistered = msg('UserRegistered', { name: 'Test' })
Here, in my article you can find more information about infering function arguments
Answered By - captain-yossarian
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.