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

Как я могу каким-то образом ввести следующую функцию processArray, просто упомянув, что предоставленный массив имеет подтип string[] без явного упоминания type1 или type2, например, с помощью arr: type1 | type2?

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

Причиной этого вызова является то, что функция будет использоваться в приложении, где существует около десяти типов объединения, каждый из которых имеет тип, который функция будет обрабатывать в качестве своего подтипа (эти типы генерируются из спецификации OpenAPI, поэтому в будущем могут быть другие, когда изменения спецификации). Я хотел обойти необходимость создавать новый тип объединения для всех этих типов или указывать их как возможные допустимые типы для аргумента функции. В идеале я хотел просто указать подтип, с которым работает функция.

function processArray<T extends string[]>(arr: T): void {
    console.info(arr);
}

const sArr = ["a", "b", "c"];
const nArr = [1, 2, 3];
const bArr = [true, false];

const arr1 = Math.random() < 0.5 ? sArr : nArr
processArray(arr1); // error, but shouldn't be

const arr2 = Math.random() < 0.5 ? sArr : bArr
processArray(arr2); // error, but shouldn't be

const arr3 = Math.random() < 0.5 ? nArr : bArr;
processArray(arr3); // error as intended, since arr3 cannot possibly be a string array

Либо processArray(arr: type1 | type2), либо processArray<T>(arr: T[])? Я в замешательстве, почему вы хотите это сделать?

Mr. Polywhirl 07.08.2023 17:11

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

xMort 07.08.2023 17:57

@jcalz Я прочитал документацию, снова проверил вашу игровую площадку и теперь понимаю, как она работает. TS не является типами Java.

xMort 08.08.2023 13:31

в более новой версии TS (4.9) есть эта вещь, которая называется «удовлетворяет оператор». Вы могли бы использовать его? typescriptlang.org/docs/handbook/release-notes/…

O-9 08.08.2023 15:35

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

xMort 08.08.2023 17:40
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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
5
63
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я изо всех сил пытаюсь понять контекст здесь. Но почему не function processArray(arr: string[] | number[] | boolean[]): void или function processArray<T extends string[] | number[] | boolean[]>(arr: T): void?

Потому что их будет 10+ и я не хотел перечислять их все, если это возможно.

xMort 08.08.2023 15:38

Я думаю, вы можете захотеть что-то вроде этого:

function processArray<T extends string | number | boolean>(arr: T[]): void {
    const [first] = arr;

    if (typeof first === "string") {
        console.info(arr); // some complex logic here
    } else {
        // do nothing for arrays that are not of string[] type
    }
}

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

if (arr.every(value => typeof value === "string")) {

Чек был просто примером. Фактический код содержит правильную защиту типа, которая определяет, является ли тип ожидаемым или нет.

xMort 08.08.2023 15:36

Моя цель - иметь только "строку" или "строку []" в сигнатуре типа функции. Я не хочу указывать имена других типов, если это возможно. Таких типов может быть много, и я хотел обойти необходимость создания типа объединения, в котором перечислялись бы все возможные типы, которые туда входят.

xMort 08.08.2023 15:38

@xMort в этом случае вы можете просто принять общий тип processArray<T>(arr: T[]) и все равно проверить, является ли каждый член строкой

Hoopra 08.08.2023 21:32
Ответ принят как подходящий

общие ограничения TypeScript служат только в качестве так называемой верхней границы типа. Если у вас есть T extends string[], то требуется, чтобы T был подтипом string[] или присваивался ему. То, что вы хотите, больше похоже на нижнюю границу, поскольку вы хотите, чтобы string[] вместо этого можно было присвоить T. То есть вы хотите, чтобы это был тип, который может принимать массив строк, а не тот, который может предоставить массив строк. На microsoft/TypeScript#14520 есть запрос функции, но пока это не является частью языка.

На самом деле похоже, что вы хотите немного другое ограничение. Вы хотите, чтобы T был каким-то типом массива, поэтому T extends readonly unknown[], и вы хотите, чтобы тип элемента этого массива, возможно, был каким-то string. Поэтому вместо того, чтобы говорить, что тип элемента ограничен string сверху или снизу, вы скорее хотите сказать, что между типом и string есть некоторое совпадение. Итак, если элемент массива равен string, или string | boolean, или "a" | "b" | "c", или "a" | number, все в порядке. Вы хотите отклонить его только в том случае, если вообще нет перекрытия, например, если тип элемента массива был number | boolean.

Поэтому нам нужен способ выразить это в ограничении. К счастью, мы можем сделать это следующим образом:

function processArray<T extends readonly unknown[] &
    (string & T[number] extends never ? never : unknown)
>(arr: T): void {
    console.info(arr);
}

Это самореферентное ограничение (также называемое F-ограниченным ), которое оказывается принятым (иногда TS отклоняет их как круговые). В нем говорится, что T должно быть присвоено readonly unknown[], и (используя [пересечение])( https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types ), нам нужно его можно присвоить условному типу string & T[number] extends never ? never : unknown. Тип string & T[number] является пересечением или перекрытием между string и типом элемента T (если T является массивом и вы индексируете его с помощью ключа типа number, то вы получаете тип элемента, поэтому T[number]). Если этот тип перекрытия пуст, это невозможный тип никогда , а условный тип оценивается как ничего не принимающий never тип. Если этот тип перекрытия не пуст, условный тип оценивается как неизвестный тип принятия всего.

Другими словами, если тип элемента T может включать в себя тип string, ограничение оценивается как T extends readonly unknown[], и он примет массив... но если это не так, то ограничение оценивается как T extends never, и все это не удастся.

Давайте проверим это:

const sArr = ["a", "b", "c"];
const nArr = [1, 2, 3];
const bArr = [true, false];

const arr1 = Math.random() < 0.5 ? sArr : nArr
processArray(arr1); // okay

const arr2 = Math.random() < 0.5 ? sArr : bArr
processArray(arr2); // okay

const arr3 = Math.random() < 0.5 ? nArr : bArr;
processArray(arr3); // error

processArray(["a", "b"] as const); // okay
processArray(["a", 1] as const); // okay
processArray([1, 2] as const); // error

Выглядит неплохо. Только массивы, которые не принимают string любого типа, отклоняются.

Площадка ссылка на код

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