类型检查 createAction Redux 助手
Typecheck createAction Redux helper
我Action
类型定义如下:
type Action =
{ type: 'DO_X' }
|
{ type: 'DO_Y', payload: string }
|
{ type: 'DO_Z', payload: number }
这是一种联合类型,其中每个成员都是一个有效的操作。
现在我想创建一个接受 type
的函数 createAction
和 returns 一个接受 payload
.
的新函数
const doZ = createAction('DO_Z')
console.log(doZ(42)) // { type: 'DO_Z', payload: 42 }
这是我当前的实现:
const createAction = (type: Action['type']) =>
(payload?: any) =>
({ type, payload })
它会像我想要的那样进行类型检查 type
。我怎样才能对 payload
进行类型检查?我希望 payload
匹配基于 type
的正确操作类型。例如,doZ
在使用 string
调用时应该会失败,因为它 payload
表示它只接受 number
.
冗长但有效:
type XAction = { type: 'DO_X', payload: undefined };
type YAction = { type: 'DO_Y', payload: string };
type ZAction = { type: 'DO_Z', payload: number };
type Action = XAction | YAction | ZAction;
const createAction = <T extends Action>(type: T['type']) =>
(payload: T['payload']) =>
({ type, payload });
// Do compile:
createAction<XAction>("DO_X")(undefined);
createAction<YAction>("DO_Y")("foo");
createAction<ZAction>("DO_Z")(5);
// Don't compile:
createAction<XAction>("DO_X")(5); // Expected `undefined`, got number
createAction<YAction>("DO_Y")(5); // Expected string, got number
createAction<ZAction>("DO_X")(5); // Expected `"DO_Z"`, got `"DO_X"`
更简单的方法(不强制createAction
的类型参数):
type Action = { type: 'DO_X', payload: undefined } | { type: 'DO_Y', payload: string } | { type: 'DO_Z', payload: number };
createAction("DO_Y")("foo");
不幸地允许 createAction<YAction>("DO_Y")(5)
等编译,因为 T
总是被推断为 Action
因此 payload
参数是 string|number|undefined
这个问题的规范答案取决于您的确切用例。我将假设您需要 Action
来准确评估您编写的类型;也就是说,type: "DO_X"
的对象 而不是 具有任何类型的 payload
属性。这意味着 createAction("DO_X")
应该是零参数的函数,而 createAction("DO_Y")
应该是单个 string
参数的函数。我还将假设您希望自动推断 createAction()
上的任何类型参数,这样您就不需要为 [=23= 的任何值指定 createAction<Blah>("DO_Z")
].如果解除这些限制中的任何一个,您可以将解决方案简化为@Arnavion 给出的解决方案。
TypeScript 不喜欢从 属性 values 映射类型,但很高兴从 属性 keys。因此,让我们以一种为我们提供编译器可以用来帮助我们的类型的方式构建 Action
类型。首先,我们像这样描述每个动作类型的有效负载:
type ActionPayloads = {
DO_Y: string;
DO_Z: number;
}
让我们也介绍任何 Action
没有有效负载的类型:
type PayloadlessActionTypes = "DO_X" | "DO_W";
(我添加了一个 'DO_W'
类型只是为了展示它是如何工作的,但你可以删除它)。
现在我们终于可以表达 Action
:
type ActionMap = {[K in keyof ActionPayloads]: { type: K; payload: ActionPayloads[K] }} & {[K in PayloadlessActionTypes]: { type: K }};
type Action = ActionMap[keyof ActionMap];
ActionMap
类型是一个对象,其键是每个 Action
的 type
,其值是 Action
联合的相应元素。它是 Action
与 payload
的交集,以及 Action
与 payload
的交集。而Action
就是ActionMap
的值类型。验证 Action
是否符合您的预期。
我们可以使用ActionMap
来帮助我们输入createAction()
函数。这是:
function createAction<T extends PayloadlessActionTypes>(type: T): () => ActionMap[T];
function createAction<T extends keyof ActionPayloads>(type: T): (payload: ActionPayloads[T]) => ActionMap[T];
function createAction(type: string) {
return (payload?: any) => (typeof payload === 'undefined' ? { type } : { type, payload });
}
这是一个重载函数,其类型参数 T
对应于您正在创建的 Action
的 type
。上面的两个声明描述了这两种情况:如果 T
是没有 payload
的 Action
的 type
,则 return 类型是零参数函数return输入 Action
的正确类型。否则,它是一个单参数函数,它采用 payload
的正确类型,return 是 Action
的正确类型。实现(第三个签名和正文)与您的类似,只是如果没有传入 payload
,它不会将 payload
添加到结果中。
大功告成!我们可以看到它按预期工作:
var x = createAction("DO_X")(); // x: { type: "DO_X"; }
var y = createAction("DO_Y")("foo"); // y: { type: "DO_Y"; payload: string; }
var z = createAction("DO_Z")(5); // z: { type: "DO_Z"; payload: number; }
createAction("DO_X")('foo'); // too many arguments
createAction("DO_X")(undefined); // still too many arguments
createAction("DO_Y")(5); // 5 is not a string
createAction("DO_Z")(); // too few arguments
createAction("DO_Z")(5, 5); // too many arguments
您可以看到这一切的实际效果 on the TypeScript Playground。希望对你有效。祝你好运!
我Action
类型定义如下:
type Action =
{ type: 'DO_X' }
|
{ type: 'DO_Y', payload: string }
|
{ type: 'DO_Z', payload: number }
这是一种联合类型,其中每个成员都是一个有效的操作。
现在我想创建一个接受 type
的函数 createAction
和 returns 一个接受 payload
.
const doZ = createAction('DO_Z')
console.log(doZ(42)) // { type: 'DO_Z', payload: 42 }
这是我当前的实现:
const createAction = (type: Action['type']) =>
(payload?: any) =>
({ type, payload })
它会像我想要的那样进行类型检查 type
。我怎样才能对 payload
进行类型检查?我希望 payload
匹配基于 type
的正确操作类型。例如,doZ
在使用 string
调用时应该会失败,因为它 payload
表示它只接受 number
.
冗长但有效:
type XAction = { type: 'DO_X', payload: undefined };
type YAction = { type: 'DO_Y', payload: string };
type ZAction = { type: 'DO_Z', payload: number };
type Action = XAction | YAction | ZAction;
const createAction = <T extends Action>(type: T['type']) =>
(payload: T['payload']) =>
({ type, payload });
// Do compile:
createAction<XAction>("DO_X")(undefined);
createAction<YAction>("DO_Y")("foo");
createAction<ZAction>("DO_Z")(5);
// Don't compile:
createAction<XAction>("DO_X")(5); // Expected `undefined`, got number
createAction<YAction>("DO_Y")(5); // Expected string, got number
createAction<ZAction>("DO_X")(5); // Expected `"DO_Z"`, got `"DO_X"`
更简单的方法(不强制createAction
的类型参数):
type Action = { type: 'DO_X', payload: undefined } | { type: 'DO_Y', payload: string } | { type: 'DO_Z', payload: number };
createAction("DO_Y")("foo");
不幸地允许 createAction<YAction>("DO_Y")(5)
等编译,因为 T
总是被推断为 Action
因此 payload
参数是 string|number|undefined
这个问题的规范答案取决于您的确切用例。我将假设您需要 Action
来准确评估您编写的类型;也就是说,type: "DO_X"
的对象 而不是 具有任何类型的 payload
属性。这意味着 createAction("DO_X")
应该是零参数的函数,而 createAction("DO_Y")
应该是单个 string
参数的函数。我还将假设您希望自动推断 createAction()
上的任何类型参数,这样您就不需要为 [=23= 的任何值指定 createAction<Blah>("DO_Z")
].如果解除这些限制中的任何一个,您可以将解决方案简化为@Arnavion 给出的解决方案。
TypeScript 不喜欢从 属性 values 映射类型,但很高兴从 属性 keys。因此,让我们以一种为我们提供编译器可以用来帮助我们的类型的方式构建 Action
类型。首先,我们像这样描述每个动作类型的有效负载:
type ActionPayloads = {
DO_Y: string;
DO_Z: number;
}
让我们也介绍任何 Action
没有有效负载的类型:
type PayloadlessActionTypes = "DO_X" | "DO_W";
(我添加了一个 'DO_W'
类型只是为了展示它是如何工作的,但你可以删除它)。
现在我们终于可以表达 Action
:
type ActionMap = {[K in keyof ActionPayloads]: { type: K; payload: ActionPayloads[K] }} & {[K in PayloadlessActionTypes]: { type: K }};
type Action = ActionMap[keyof ActionMap];
ActionMap
类型是一个对象,其键是每个 Action
的 type
,其值是 Action
联合的相应元素。它是 Action
与 payload
的交集,以及 Action
与 payload
的交集。而Action
就是ActionMap
的值类型。验证 Action
是否符合您的预期。
我们可以使用ActionMap
来帮助我们输入createAction()
函数。这是:
function createAction<T extends PayloadlessActionTypes>(type: T): () => ActionMap[T];
function createAction<T extends keyof ActionPayloads>(type: T): (payload: ActionPayloads[T]) => ActionMap[T];
function createAction(type: string) {
return (payload?: any) => (typeof payload === 'undefined' ? { type } : { type, payload });
}
这是一个重载函数,其类型参数 T
对应于您正在创建的 Action
的 type
。上面的两个声明描述了这两种情况:如果 T
是没有 payload
的 Action
的 type
,则 return 类型是零参数函数return输入 Action
的正确类型。否则,它是一个单参数函数,它采用 payload
的正确类型,return 是 Action
的正确类型。实现(第三个签名和正文)与您的类似,只是如果没有传入 payload
,它不会将 payload
添加到结果中。
大功告成!我们可以看到它按预期工作:
var x = createAction("DO_X")(); // x: { type: "DO_X"; }
var y = createAction("DO_Y")("foo"); // y: { type: "DO_Y"; payload: string; }
var z = createAction("DO_Z")(5); // z: { type: "DO_Z"; payload: number; }
createAction("DO_X")('foo'); // too many arguments
createAction("DO_X")(undefined); // still too many arguments
createAction("DO_Y")(5); // 5 is not a string
createAction("DO_Z")(); // too few arguments
createAction("DO_Z")(5, 5); // too many arguments
您可以看到这一切的实际效果 on the TypeScript Playground。希望对你有效。祝你好运!