Typescript обеспечивает тип возвращаемого значения функции как ключ интерфейса на основе параметра

По сути, я пытаюсь добиться, чтобы функция возвращала правильный тип на основе ключа, переданного функции. Поэтому, если ключ service1, правильный тип возврата должен быть Payloads['service1']. Как я могу этого добиться?

interface Payloads {
    service1: {
        a: boolean;
    };
    service2: {
        b: string;
    };
    service3: {
        c: number;
    };
};

const createPayload = <S extends keyof Payloads>(key: S): Payloads[S] => {
    switch (key) {
        case 'service1': return { a: true }
        case 'service2': return { b: 'e' }
        case 'service3': return { c: 3 }
        default: throw new Error('undefined service')
    }
}

Ошибка, которую я получаю:

Typescript обеспечивает тип возвращаемого значения функции как ключ интерфейса на основе параметра

Ссылка на игровую площадку TypeScript

Обратите внимание, что ваш вопрос, как задано, "почему эта ошибка", а не "как ее исправить". Ответ на заданный вопрос таков: «Потому что компилятор не может сузить параметры универсального типа, такие как S, с помощью анализа потока управления, и поэтому компилятор ошибается в сторону консервативности в соответствии с мс/ТС#30769». Если вам также нужен способ сделать это с помощью рефакторинга для безопасности типов, вы можете захотеть редактировать вопрос, чтобы сказать об этом явно.

jcalz 03.04.2022 17:27

В этой заметке, если вы ищете альтернативный подход, который поддерживает безопасность типов, вы можете провести рефакторинг для индексирования в объект, такой как это. Это соответствует вашим потребностям? Если это так, я могу написать ответ (при условии, что вопрос отредактирован, чтобы запросить такой ответ). Если нет, то что мне не хватает?

jcalz 03.04.2022 17:27

@jcalz, не могли бы вы предоставить ссылку, объясняющую, почему эта индексация объектов решает проблему, пожалуйста?

Andrea Simone Costa 04.04.2022 09:25

В последнем абзаце моего ответа есть эта информация (вы также можете прочитать на typescriptlang.org/docs/handbook/release-notes/…)

jcalz 04.04.2022 15:00
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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
4
88
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

TypeScript не знает тип S в теле функции, поэтому он ожидает, что вы передадите свойства все, чтобы убедиться, что Payloads[S] выполняется. Но вы можете обмануть TypeScript! Если изменить тип возвращаемого значения на Payloads[keyof Payloads], это означает один из вариантов, и вы не получите никакой ошибки.

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

function createPayload<S extends keyof Payloads>(key: S): Payloads[S];
function createPayload<S extends keyof Payloads>(key: S): Payloads[keyof Payloads] {
    // code here...
}

Это показывает Payloads[S] вызывающей стороне, но внутри мы ожидаем Payloads[keyof Payloads]. Полный рабочий пример здесь.

Но вы потеряли немного безопасности: case 'service2': return { c: 3 } теперь принимается

Andrea Simone Costa 03.04.2022 15:27

Это верно, но я не знаю другого способа сделать это. Я думаю, что это просто невозможно с TypeScript.

blaumeise20 03.04.2022 15:31

Я согласен с @AndreaSimoneCosta, компилятор теперь позволяет возвращать объект со всеми объединенными свойствами, тогда как я хочу явно «указать» компилятору принимать только один тип из интерфейса Payloads.

xkcm 03.04.2022 15:34
Ответ принят как подходящий

Тип Payloads[S] — это индексированный тип доступа, который зависит от еще не определенного параметра типа универсальныйS.

В вашей реализации компилятор не может использовать анализ потока управления для сужения параметра типа S в операторе switch/case. Видит, что key можно сузить, например, до "service1", но не сужает параметр типа S. И поэтому он не знает, что { a: true } будет присвоено Payloads[S] в этом случае. Компилятор здесь слишком осторожен; единственное, что было бы приятно вернуть, — это значение, которое можно присвоить Payloads[S] независимо от того, что такое S, которое оказывается перекресток всех типов значений, эквивалентным {a: boolean; b: string; c: number}. Поскольку вы никогда не возвращаете такое значение, компилятор жалуется.

В GitHub есть несколько открытых проблем, требующих некоторого улучшения. См., например, Майкрософт/TypeScript#33014. Однако на данный момент (начиная с TS4.6), если вы должны написать код, который работает таким образом, то компилятор не поможет вам проверить безопасность типов. Вам нужно будет взять на себя ответственность, используя что-то вроде утверждения типа

const createPayloadAssert = <S extends keyof Payloads>(key: S): Payloads[S] => {
    switch (key) {
        case 'service1': return { a: true } as Payloads[S]
        case 'service2': return { b: 'e' } as Payloads[S]
        case 'service3': return { c: 3 } as Payloads[S]
        default: throw new Error('undefined service')
    }
}

или один позывной перегрузка

function createPayloadOverload<S extends keyof Payloads>(key: S): Payloads[S];
function createPayloadOverload(key: keyof Payloads) {
    switch (key) {
        case 'service1': return { a: true };
        case 'service2': return { b: 'e' };
        case 'service3': return { c: 3 };
        default: throw new Error('undefined service')
    }
}

чтобы ослабить вещи достаточно, чтобы предотвратить ошибку. Это обязательно позволяет вам совершать ошибки, когда вы случайно переключаете возвращаемые значения. Но пока это лучшее, что вы можете сделать с switch/case.


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

const createPayload = <S extends keyof Payloads>(key: S): Payloads[S] => ({
    service1: { a: true },
    service2: { b: 'e' },
    service3: { c: 3 }
}[key]);

const createPayloadBad = <S extends keyof Payloads>(key: S): Payloads[S] => ({
    service1: { a: true },
    service2: { a: true }, // <-- error!
    service3: { c: 3 }
}[key]);

Это работает, потому что, среди прочего, индексированные типы доступа были познакомился с TypeScript (называемые "типами поиска" в этой ссылке), чтобы представить на уровне типа то, что происходит, когда вы индексируете объект с ключом на ценностном уровне. То есть, если у вас есть объектно-подобное значение t типа T и ключеподобное значение k типа K, то при индексировании в t с k как t[k] свойство, которое вы читаете, будет иметь тип T[K]. Поэтому, если вы хотите, чтобы компилятор знал, что у вас есть значение типа Payloads[S], вы можете сделать это, проиндексировав значение типа Payloads с ключом типа S, как показано выше.

Ссылка на код для игровой площадки

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