代码之家  ›  专栏  ›  技术社区  ›  daGrevis

Typecheck createAction Redux助手

  •  11
  • daGrevis  · 技术社区  · 7 年前

    我有 Action 类型定义如下:

    type Action =
        { type: 'DO_X' }
        |
        { type: 'DO_Y', payload: string }
        |
        { type: 'DO_Z', payload: number }
    

    它是一种联合类型,其中每个成员都是有效的操作。

    createAction 这是可以接受的 type 并返回一个新函数,该函数接受 payload

    const doZ = createAction('DO_Z')
    console.log(doZ(42)) // { type: 'DO_Z', payload: 42 }
    

    以下是我当前的实现:

    const createAction = (type: Action['type']) =>
      (payload?: any) =>
        ({ type, payload })
    

    类型 有效载荷 ? 我想要 有效载荷 类型 例如 doZ 当使用 string 因为它是 说它只接受 number .

    2 回复  |  直到 7 年前
        1
  •  4
  •   jcalz    7 年前

    这个问题的规范答案取决于您的具体用例。我假设你需要 Action 准确评估您编写的类型;也就是说,一个 type: "DO_X" 有一个 payload 任何类型的财产。这意味着 createAction("DO_X") createAction("DO_Y") 应该是单个 string 论点我还假设您需要任何类型参数 createAction() 自动推断,以便您不需要指定 createAction<Blah>("DO_Z") 对于任何值 Blah 。如果解除其中任何一个限制,您可以将解决方案简化为@Arnavion给出的解决方案。


    价值观 ,但它很高兴从房地产方面这样做 .那么,让我们构建

    type ActionPayloads = {
      DO_Y: string;
      DO_Z: number;
    }
    

    我们也来介绍一下 行动 没有有效负载的类型:

    type PayloadlessActionTypes = "DO_X" | "DO_W";
    

    (我添加了一个 'DO_W' 键入只是为了显示其工作原理,但您可以删除它)。

    现在我们终于能够表达 行动

    type ActionMap = {[K in keyof ActionPayloads]: { type: K; payload: ActionPayloads[K] }} & {[K in PayloadlessActionTypes]: { type: K }};
    type Action = ActionMap[keyof ActionMap];
    

    这个 ActionMap 类型是一个对象,其键是 type 每个的 行动 行动 s与 有效载荷 s、 和 行动 没有 s、 和 行动 只是 .验证 行动 这就是你所期望的。

    我们可以使用 帮助我们键入 作用这是:

    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 属于 您正在创建。前两个声明描述了这两种情况:如果 T 类型 行动 没有 。否则,它是一个单参数函数,采用正确的类型 有效载荷 并返回正确类型的 行动 有效载荷 如果没有 有效载荷 已通过。


    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

        2
  •  1
  •   Arnavion Tim Pote    7 年前

    冗长,但这是可行的:

    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) etc编译,因为 T 总是推断为 Action payload 参数为 string|number|undefined