【问题标题】:Extracting type from union of types based on a discriminator基于鉴别器从类型联合中提取类型
【发布时间】:2019-03-05 01:10:33
【问题描述】:

在下面的示例中,如何为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

【问题讨论】:

    标签: typescript typescript2.0 typescript3.0


    【解决方案1】:

    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'

    【讨论】:

      【解决方案2】:

      有两种方法可以做到这一点。

      你可以声明一次类型:

      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;
          },
      };
      

      声明一次类型的好处显然是您不必一遍又一遍地做同样的事情,但单独描述它们可以让您从推断这些函数的返回类型中受益。

      更新:正如评论中提到的 Titian Cernicova-Dragomir,您可以将其声明为要在其他地方重用的类型:

      type ReducerMap<T extends { type: string }> = { [P in T['type']]: (action: Extract<T, { type: P }>) => any }
      

      函数的返回类型是any。 我试图找到一种方法来推断每个定义的实际返回值,但这不太可能。

      而且由于它被用作reducer map,你可能不会关心返回值,因为它被框架使用了。

      【讨论】:

      • 或者您可以创建一个泛型类型以供将来使用:type AllActions&lt;T extends { type: string }&gt; = { [P in T['type']]: (action: Extract&lt;T, { type: P }&gt;) =&gt; void }
      • 好主意。我也在研究如何让它推断出返回值。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-10
      • 2018-01-18
      • 1970-01-01
      相关资源
      最近更新 更多