У меня есть типы, которые обеспечивают связь между обоими членами кортежа в массиве этих кортежей, когда они передаются в качестве аргумента функции, т. е. второй член — это функция, которая принимает параметр данных, соответствующий первому члену. Детская площадка
Сначала проверяем, сохраняются ли отношения 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 в мой вариант использования с ограниченными типами кортежей, но неспособная обеспечить работу автозаполнения. Обновленная игровая площадка
Нет, я просто исследовал способы достижения конечной цели - иметь возможность передавать массив из этих двух кортежей-членов с ошибкой типа, когда связь между этими членами разрывается, и в то же время автоматически выводить тип параметра функции без необходимости указывать его тип.
В настоящее время вы не можете добиться этого из-за ms/TS#53018. То, что вы делаете, — это «вывод из сопоставленных типов» или «обратно сопоставленных типов», а контекстно-зависимый параметр обратного вызова в каждом свойстве в настоящее время не может быть выведен. Я был бы рад написать ответ, говорящий об этом. Вам придется обойти это, используя отдельные общие функции для каждого члена вашего массива (похоже, именно такой подход используется в ответе ниже). Как нам действовать?
На данный момент я решил эту проблему, изменив параметр из массива кортежей на тип функции, который предоставляет вспомогательную функцию, поэтому вы вызываете ее как fun(helper => [helper('string', str => 'result'), что не идеально, но, вероятно, вариант, который мне нравится больше всего. Если вы считаете, что разработка этого вопроса будет полезна другим, не стесняйтесь добавлять его, и я приму его.
Ой, подождите, может быть, у вас на самом деле нет полной версии ms/TS#53018. Соответствует ли эта версия вашим потребностям? Если да, то я напишу ответ с объяснением; если нет, то что мне не хватает?
Похоже, я совершил здесь большую ошибку и каким-то образом пропустил этот путь к решению. Тип вашей игровой площадки оказался именно тем, что мне нужно, большое спасибо.
Я поместил ссылку на игровую площадку в нижней части моего вопроса, немного расширив ваш код - проверка типов работает, я связал ее с типом ограничения данных, это работает при проверке типов, однако автозаполнение не работает, поскольку я не могу получить поля объекта введите, чтобы показать. Я снова столкнулся с проблемой обратного сопоставления типов?
Это дополнительный вопрос? Если да, то можем ли мы уложить его спать, прежде чем погрузиться в него? Или это необходимая часть для ответа на этот текущий вопрос, и в этом случае вы могли бы отредактировать вопрос так, чтобы проблема была в открытом тексте, а не просто в ссылке на игровую площадку? Кроме того, из этой ссылки не сразу понятно, где вы ожидаете автозаполнения. Я предпочитаю сначала записать ответ, который я предложил, а затем мы сможем рассмотреть автозаполнение в новом вопросе, если оно сохранится, но я подожду, чтобы услышать, что вы хотите сделать.
Я мог бы задать новый вопрос и привести рабочий случай, в котором параметром является функция, возвращающая массив кортежей, а не просто массив кортежей, чтобы лучше проиллюстрировать проблему. Что касается автозаполнения в новой игровой площадке — в объединении есть и тип объекта, поэтому, как только я напишу фигурные скобки, мне нужно будет получить параметры автозаполнения для обоих реквизитов, которых я сейчас не получаю. Он проверяет и выдает ошибки, если реквизиты отсутствуют, но никогда их не предлагает.
Вы могли бы написать эту версию возможно; моя склонность ставить H extends D[]
приводит к сбою вывода, поэтому мне нужно написать H extends any[]
, а затем пересечь каждый элемент H
с D
. Не уверен, что это соответствует вашим потребностям.
С точки зрения функциональности это настолько близко, насколько это возможно, к тому, что я хотел, кажется, единственная проблема чисто косметическая (что довольно большая проблема), поскольку это полностью запутывает тип в IDE, думаете ли вы, что любое преобразование к этому будет можно ли выровнять результат? Я попробовал несколько типов «украшения», но они, похоже, не очень хорошо сочетаются с пересечениями.
Основываясь на вашем комментарии, я нашел альтернативный подход, хотя он требует использования вспомогательной функции для определения кортежей:
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 будет выводить только первый найденный тип кортежа и считать, что все дальнейшие элементы массива должны соответствовать первому.
Спасибо за предложение, я рассматривал для этого какой-то вариант служебной функции, но мне все еще интересно, можно ли решить эту проблему полностью в рамках системы типов.
Такой тип вывода, когда вам нужен 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.
Вам нужно решение, позволяющее использовать ваши типы
TupleDataFn
иArrayOfTuples
, потому что вы уже используете их где-то еще? Или вам просто нужно что-то, что позволит достичь вашей конечной цели в функцииtest
?