Извлечение типа из объединения типов на основе дискриминатора - PullRequest
0 голосов
/ 29 сентября 2018

В приведенном ниже примере, как я могу обеспечить правильную ввод для аргумента 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 голосов
/ 30 сентября 2018

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'.

0 голосов
/ 29 сентября 2018

Есть два способа сделать это.

Вы можете объявить тип один раз:

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

Преимущество объявления типа один раз заключается в том, что вам не нужно делать одно и то же снова и снова, но их индивидуальное описание позволяет вам извлечь выгоду из определения типа возврата этих функций.

ОБНОВЛЕНИЕ: как Тициан Черникова-Драгомир упоминается в комментарии, вы можете объявить его как тип для повторного использования в другом месте:

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

Тип возвращаемого значения функции - any.Я попытался найти способ вывести это на фактическое возвращаемое значение каждого определения, но это вряд ли возможно.

И поскольку он используется в качестве карты редуктора, вы, вероятно, не будете заботиться о возвращаемом значениикак это потребляется каркасом.

...