Сохранение информации о типе при обеспечении связи типов между членами кортежа при использовании в параметре функции сопоставленного типа

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

Сначала проверяем, сохраняются ли отношения Tuple:

export type TupleDataFn<Type>
    = Type extends [infer Data, infer Fn]
    ? Fn extends ((data: Data) => any)
        ? Type
        : never
    : never

Сопоставленный тип, который применяет проверку ко всем элементам массива:

export type ArrayOfTuples<A>
    = {[K in keyof A]: TupleDataFn<A[K]>}

Тестовая функция

export const test = <H extends [any, any][]>(handler: ArrayOfTuples<H>) => {}

И, наконец, использование — здесь параметр функции без

test([
    // I can do this explicitly and this works as expected
    ['explicit', (data: string) => {}],
    // in remaining cases, the data is 'any'
    // instead of infered types matching the first member
    ['text', (data) => {}],
    [42, (data: string) => {}],
    [{ count: 10, data: "test"}, (data) => {}]
])

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

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

Ссылка на игровую площадку, пытающаяся включить код @jcalz в мой вариант использования с ограниченными типами кортежей, но неспособная обеспечить работу автозаполнения. Обновленная игровая площадка

Вам нужно решение, позволяющее использовать ваши типы TupleDataFn и ArrayOfTuples, потому что вы уже используете их где-то еще? Или вам просто нужно что-то, что позволит достичь вашей конечной цели в функции test?

Jesus Diaz Rivero 23.07.2024 13:22

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

Jarek 23.07.2024 13:38

В настоящее время вы не можете добиться этого из-за ms/TS#53018. То, что вы делаете, — это «вывод из сопоставленных типов» или «обратно сопоставленных типов», а контекстно-зависимый параметр обратного вызова в каждом свойстве в настоящее время не может быть выведен. Я был бы рад написать ответ, говорящий об этом. Вам придется обойти это, используя отдельные общие функции для каждого члена вашего массива (похоже, именно такой подход используется в ответе ниже). Как нам действовать?

jcalz 23.07.2024 14:35

На данный момент я решил эту проблему, изменив параметр из массива кортежей на тип функции, который предоставляет вспомогательную функцию, поэтому вы вызываете ее как fun(helper => [helper('string', str => 'result'), что не идеально, но, вероятно, вариант, который мне нравится больше всего. Если вы считаете, что разработка этого вопроса будет полезна другим, не стесняйтесь добавлять его, и я приму его.

Jarek 23.07.2024 15:59

Ой, подождите, может быть, у вас на самом деле нет полной версии ms/TS#53018. Соответствует ли эта версия вашим потребностям? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?

jcalz 23.07.2024 18:24

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

Jarek 24.07.2024 07:56

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

Jarek 24.07.2024 09:18

Это дополнительный вопрос? Если да, то можем ли мы уложить его спать, прежде чем погрузиться в него? Или это необходимая часть для ответа на этот текущий вопрос, и в этом случае вы могли бы отредактировать вопрос так, чтобы проблема была в открытом тексте, а не просто в ссылке на игровую площадку? Кроме того, из этой ссылки не сразу понятно, где вы ожидаете автозаполнения. Я предпочитаю сначала записать ответ, который я предложил, а затем мы сможем рассмотреть автозаполнение в новом вопросе, если оно сохранится, но я подожду, чтобы услышать, что вы хотите сделать.

jcalz 24.07.2024 15:16

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

Jarek 24.07.2024 16:08

Вы могли бы написать эту версию возможно; моя склонность ставить H extends D[] приводит к сбою вывода, поэтому мне нужно написать H extends any[], а затем пересечь каждый элемент H с D. Не уверен, что это соответствует вашим потребностям.

jcalz 25.07.2024 15:22

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

Jarek 26.07.2024 19:45
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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 для повышения производительности приложения путем загрузки модулей только тогда, когда они...
1
11
98
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

type Tuple<T> = [val: T, fn: (data: T) => void];
const test = (params: Tuple<any>[]) => {};
const createTuple: <T>(val: T, fn: (data: T) => void) => [T, typeof fn] = (val, fn) => [val, fn];

test([
    createTuple('explicit', (data) => { console.info(data); }), // string
    createTuple('text', (data) => { console.info(data); }), // string
    createTuple(42, (data) => { console.info(data); }), // number
    createTuple({ count: 10, data: "test"}, (data) => { console.info(data); }), // { count: number; data: string; }
]);

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

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

Jarek 23.07.2024 14:23
Ответ принят как подходящий

Такой тип вывода, когда вам нужен TypeScript для одновременного вывода аргументов общего типа и неаннотированных типов параметров обратного вызова контекстуально, является болевой точкой в ​​TypeScript. Алгоритм вывода TypeScript использует различные эвристики, которые хорошо работают в широком диапазоне сценариев, но если у вас есть контекстные и универсальные типы, которые зависят друг от друга, алгоритм вывода не всегда находит подходящий набор типов, даже если человек может это сделать. посмотреть, как это сделать. Иногда вывод оказывается «частично» успешным в том смысле, что контекстный тип может быть выведен правильно, а соответствующий аргумент универсального типа, который от него зависит, — нет (и он возвращается к чему-то менее полезному, например unknown).

В microsoft/TypeScript#47599 есть открытая проблема с просьбой об улучшениях в таких ситуациях, и такие улучшения происходят постоянно, например microsoft/TypeScript#48538, но всегда будут пробелы. И поддержка, которая есть, иногда оказывается хрупкой перед лицом, казалось бы, незначительных изменений. Таким образом, метод, который работает в конкретном случае, может не работать в несколько другом.

Вообще говоря, вы можете захотеть провести рефакторинг, чтобы освободить TypeScript от необходимости делать такой одновременный вывод, например, используя компоновщики, чтобы делать что-то поэтапно, а не все сразу. Но это выходит за рамки заданного вопроса.


Для кода в заданном вопросе я бы написал

const test =
    <H extends any[]>(tuples: { [I in keyof H]: [H[I], (data: H[I]) => void] }) => { }

где функция является универсальной в H, кортеже ️ типов данных, и где параметр tuples представляет собой тип кортежа, сопоставленный над H. Итак, если H — это [X, Y, Z], то tuples — это [[X, (data: X)=>void], [Y, (data: Y)=>void], [Z, (data: Z)=>void]]. И поэтому, когда вы вызываете test(), мы хотим, чтобы TypeScript вывел H из сопоставленного типа tuples.

Давай попробуем:

test([
    ['explicit', (data: string) => { data.toUpperCase() }],
    ['text', (data) => { data.toUpperCase() }],
    [42, (data: string) => { }], // error!
    [{ count: 10, data: "test" }, (data) => { data.count }]
])

Выглядит неплохо. Здесь H подразумевается как [string, string, number, { count: number; data: string;}]. Явно аннотированный data в первом элементе, как и прежде, подойдет. Но теперь неаннотированные data во втором и четвертом элементах также в порядке и выводятся как соответствующий тип из H. А неправильно аннотированный data в третьем элементе дает ожидаемую ошибку.

Так что все работает так, как ожидалось. Но опять же, это может быть нестабильно из-за незначительных изменений в коде, поскольку такие вещи, как правило, находятся на грани возможностей вывода TypeScript.

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

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

Jarek 25.07.2024 13:01

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