Тип возвращаемого значения Typescript в зависимости от параметра объекта

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

Я пробовал следующее с условными типами, но это не работает (см. встроенный комментарий для ошибки машинописного текста):

Площадка для TypeScript

type Status = 'statusType'
type GoalActivity = 'goalActivityType'

type Argument = { type: 'status'; status: Status | null } | { type: 'goalActivity'; goalActivity: GoalActivity | null }

const handleReaction = (arg: Argument): Argument extends { type: "status" } ? Status : GoalActivity => {
    if (arg.type === 'status') {
        return 'statusType' // Type '"statusType"' is not assignable to type '"goalActivityType"'.
    } else {
        return 'goalActivityType'
    }
}

Я также пробовал следующее, используя форму перегрузки функций для стрелочных функций (, как описано здесь ), но это также приводит к ошибке TypeScript, а также использует «любой», который теряет большую часть преимуществ ввода внутри определения функции. :

Площадка для TypeScript

type Status = 'statusType'
type GoalActivity = 'goalActivityType'

type HandleReaction = {
    (arg: { type: 'status'; status: Status | null }): Status
    (arg: { type: 'goalActivity'; goalActivity: GoalActivity | null }): GoalActivity
}

const handleReaction: HandleReaction = (arg: any) => { // Type '"goalActivityType"' is not assignable to type '"statusType"'.
    if (arg.type === 'status') {
        return 'statusType'
    } else {
        return 'goalActivityType'
    }
}

Этот вопрос похож на этот, но с той разницей, что параметр функции является объектом.

Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
2
0
3 127
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Проблема

Во-первых, вы не использовали универсальный тип для своего аргумента, в результате чего typescript никогда не выведет правильный тип на основе вашего ввода (вы можете представить, что общий тип является параметром, tsc требует, чтобы он вычислял результат на основе вашего ввода) .

Суммируя,

const handleReaction = (arg: Argument): Argument extends { type: "status" } ? Status : GoalActivity => { // ... }

всегда будет возвращать Status | GoalActivity как возвращаемый тип.

Решение

Конечно, вы должны использовать общий тип здесь в качестве аргумента. Я разделю ваш код со встроенным объяснением:

type Status = 'statusType'
type GoalActivity = 'goalActivityType'

type StatusObj = { type: 'status'; status: Status | null };
type GoalActivityObj = { type: 'goalActivity'; goalActivity: GoalActivity | null }

type Argument = StatusObj | GoalActivityObj;

// Define returned type based on a input argument `T`
type ReturnType<T> = T extends StatusObj ? Status : GoalActivity;

// Generic type should be used here
const handleReaction = <T extends Argument>(arg: T): ReturnType<T> => {
    if (arg.type === 'status') {

        // Q: Why do we have to cast here?
        // A: Any returned type can't assign to statement of `type ReturnType<T> ...`
        // but luckily `tsc` still allows us to cast back since they are all string literal
        return 'statusType' as ReturnType<T>;

    } else {
        return 'goalActivityType' as ReturnType<T>;
    }
}

А, кажется, я понимаю. Жаль, что нам нужно привести возвращаемое значение, но я предполагаю, что это ограничение TypeScript.

Robert Cooper 24.12.2020 20:00

Ваши объекты-аргументы должны иметь свои собственные типы. Как только они это сделают, вы можете использовать перегрузку функций следующим образом:

type Status = { type: 'status', status: 'statusValue' };
type GoalActivity = { type: 'goalActivity', goalActivity: 'goalValue' };

function handleReaction(arg: Status): Status['status']
function handleReaction(arg: GoalActivity): GoalActivity['goalActivity']
function handleReaction(arg: Status | GoalActivity) { 
    if ('status' in arg) {
        return arg.status;
    } else {
        return arg.goalActivity;
    }
}

const a = handleReaction({ type: 'status', status: 'statusValue' })
//    ^? 'statusValue'
const b = handleReaction({ type: 'goalActivity', goalActivity: 'goalValue' })
//    ^? 'goalValue'

Детская площадка

Другие вопросы по теме