根据鉴别符从类型并集中提取类型

时间:2018-09-29 05:37:13

标签: typescript typescript2.0 typescript3.0

在下面的示例中,如何为action中的withoutSwitchReducer参数提供正确的键入?

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

2 个答案:

答案 0 :(得分:0)

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

您可以一次声明类型:

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。 我试图找到一种方法来推断每个定义的实际返回值,但这不太可能。

并且由于它被用作化简器映射,因此您可能不会在乎框架所消耗的返回值。

答案 1 :(得分:0)

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'