Типы TypeScript — доступ к аргументам функции Тип кортежа из вызова функции (тип Fn, сгенерированный другим типом)

Редактировать: я полностью изменил вопрос после того, как сегодня немного поиграл с этим: Детская площадка

Я начинаю с конца, с примера, который для простоты хочет получить правильный первый аргумент, предоставленный пользователем:

const functionWithoutRestrictedParams = <Args extends any[]>(...args: Args): Args[0] => {
    return '' as any
}

const stringResult = functionWithoutRestrictedParams('first', 'second')

const undefinedResult = functionWithoutRestrictedParams(undefined, 'second')

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

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

export type FnWithOptions<A extends any[]> = {[I in keyof A]: A[I] | undefined}

type BasicFn<Params extends any[]>
    = (...args: FnWithOptions<Params>) => void


const fn:BasicFn<[first: string, second: number]> = (first, second) => {}

fn('test', 1)
fn(undefined, 1)
//@ts-expect-error
fn(1,1)

Это сделано для того, чтобы показать, какие типы используются API. Каждый параметр может быть необязательным.

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

type MockTransformer<Params, Args> = Params

type Attempt<Params extends any[]>
    = <Args extends any[]>(...args:MockTransformer<FnWithOptions<Params>, Args>) => Args[0]

const attempt:Attempt<[first: string, second: number]> = (first, second) => {}

const expectedString = attempt("str", 1)
const expectedUndefined = attempt(undefined, 1)

Здесь я попытался объединить два предыдущих примера, сначала определив необходимые параметры, которые всегда предоставляются при определении функции. Затем определение типа функции. Я рассуждал здесь о типе MockTransformer, который принимает Params и Args, возвращает Params, так что это тип данных, который может предоставить пользователь. Затем я надеялся, что фактические аргументы, с которыми вызывается функция, будут привязаны к Args с сохраненной информацией о типе, и что я получу правильный тип возврата: неопределенный или строковый, в зависимости от предоставленного значения. Однако это «любой» тип. Можно ли что-то сделать в этой ситуации или мне просто не повезло?

Каков твой вопрос?

Caleth 01.07.2024 12:27

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

Jarek 01.07.2024 13:56

@jcalz Сегодня я попробовал немного поиграть с этим и обновил свой вопрос

Jarek 02.07.2024 13:40

Соответствует ли такой подход вашим потребностям? Обратите внимание, что часть, в которой сохраняется тип первого аргумента, почти не имеет ничего общего с частью, в которой вы разрешаете добавлять undefined. Я бы даже не стал ставить это в вопрос; вы в основном решили ту часть, где вы разрешаете неопределенное значение типа FnWithOptions. Может быть, вы могли бы отредактировать, чтобы полностью удалить его? Но в любом случае дайте мне знать, если мое предложение выше полностью решает вопрос. Если да, то я напишу ответ; если нет, то что мне не хватает?

jcalz 02.07.2024 14:56

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

Jarek 02.07.2024 18:52

Было бы здорово, если бы вы случайно могли уточнить часть «<A расширяет FnWithOptions<P>>(...args: A)» - я не знаю, как это лучше всего описать, но, например, «< A расширяет любой[]>(...args: A)" интуитивно понятен - A определил ограничение, и компилятор заменяет его простым для понимания способом, первый, однако, не интуитивно понятен, поскольку кажется, что он ссылается на самого себя, что каким-то образом работает, но я действительно не видел, чтобы это много документировалось или обсуждалось, и работаю с этим на основе попытки/неудачи/повторения.

Jarek 02.07.2024 20:21

Я объяснил это в своем ответе, как мог.

jcalz 02.07.2024 20:34
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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 для повышения производительности приложения путем загрузки модулей только тогда, когда они...
0
7
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

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

<A extends P>(...args: A) => void

Теперь, если вы вызовете функцию, TypeScript выведет A на основе типов фактических аргументов. Типы этих аргументов ограничены до P, поэтому, если A равно [string], то вы не можете вызвать функцию с number.

Если функция должна возвращать тип этого первого аргумента, вы можете вернуть индексированный тип доступа A[0]:

<A extends P>(...args: A) => A[0]

В вашем конкретном случае вы на самом деле хотите принять не P, а модифицированную версию P, где каждый элемент может быть undefined. Таким образом, вместо A extends P вам нужно A extends UndefinableElements<P>, где UndefinableProps — это отображаемый массив/тип кортежа, определенный как:

type UndefinableElements<T extends any[]> =
    { [I in keyof T]: T[I] | undefined }

Итак, если P — это [string], то UndefinableElements<P> — это [string | undefined]. Итак, ваш тип функции выглядит так:

type Attempt<P extends any[]> =
    <A extends UndefinableElements<P>>(...args: A) => A[0]

Опять же, здесь происходит то, что Attempt<P> превратит тип кортежа P в версию, в которой все элементы также могут принимать undefined, а затем он станет универсальной функцией, в которой список аргументов представляет собой некоторый общий тип A, ограниченный модифицированной версией P, и где он возвращает тип первого аргумента A[0]. Давайте проверим это:

const attempt: Attempt<[first: string, second: number]> =
    (first, second) => first

const expectedString = attempt("str", 1);
//    ^? const expectedString: "str"

const expectedUndefined = attempt(undefined, 1);
//    ^? const expectedUndefined: undefined

attempt(0, 1); // error!
//      ~
// Argument of type 'number' is not assignable to parameter of type 'string'.

Выглядит неплохо. Первый вызов A выведен как ["str", 1] и, таким образом, A[0] есть "str", тогда как второй вызов A выведен как [undefined, 1] и, следовательно, A[0] есть undefined. А третий вызов выдает ошибку, поскольку тип [0, 1], который TypeScript хочет определить для A, не соответствует UndefinableElements<[first: string, second: number]>, то есть [first: string | undefined, second: number | undefined].

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

Спасибо за хороший ответ, как всегда полезно.

Jarek 02.07.2024 21:19

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

Может ли интерфейс Go получать какую-либо функцию независимо от его сигнатуры?
Обработка динамических выходных данных в функциях Swift без ущерба для безопасности типов (любая)
Использование протокола как типа должно быть записано с любыми (целями обучения)
Какой смысл имеет оператор стрелки в Haskell в функциях, принимающих более одного параметра?
Как исправить столбец с числовыми значениями, который воспринимается как строковое поле из-за пустых строк в фрейме данных Pandas?
Как создать собственный тип для анализа [u8;32] из json, содержащего шестнадцатеричную строку, в Rust
Ограничить тип возвращаемого значения подтипом этого
Компиляция карты времени имени строки типа в типы в C++
Вычисление чисел, для которых требуется тип данных размером более 16 байт в C++
Введите 'строка | логическое значение не может быть назначено для ввода «никогда» в машинописном тексте