Почему сопоставление массива не работает с той же записью сопоставления рекламы?

Есть ли причина, по которой отображение массива не работает так же, как отображение записи? Массив сам по себе является итерируемым объектом, как и Record<number, ...>, верно?

В этом коде вы можете наблюдать желаемое поведение ParamRecordRecord. Это дает мне таблицу данных с методами, у которых параметр автоматически выводится на основе контекста данных.

Еще мне нужна версия в виде записи для массивов. Таким образом, ParamRecordArray является точной копией ParamRecordRecord, за исключением того, что вместо ключей, перечисленных в enum BBB, ключи являются индексами массива. Однако по какой-то причине я не могу воспроизвести то же поведение, что и в таблице выше. Все параметры в компонентах методов являются числами.

пометка красного цвета🔴, где возникла проблема

enum AAA { a1 = 'a1', a2 = 'a2', a3 = 'a3' }
enum BBB { b1 = 'b1', b2 = 'b2', b3 = 'b3' }

// the data structure
type TestData<T extends number = number> = {
    _: string;
    components?: T[];
    check: (c: T) => void;
}

// version record of record

type ParamRecordRecord<T> = {
    [KA in keyof T & AAA]: {
        [KB in keyof T[KA] & BBB]?: TestData<Extract<T[KA][KB], number> extends never ? number : Extract<T[KA][KB], number>>
    }
}

new class TestRecorRecord<T> {
    constructor(data: ParamRecordRecord<T>) { }
}(
    {
        [AAA.a1]: {
            [BBB.b1]: {
                _: '',
                check: (c) => { } //🟢number
                //     ^?
            },
        },
        [AAA.a2]: {
            [BBB.b2]: {
                _: '',
                components: [1],
                check: (c) => { }//🟢1
                //     ^?
            },
            [BBB.b1]: {
                _: '',
                components: [2],
                check: (c) => { }//🟢2
                //     ^?
            },
        },
    }
)



// version record of array

//This is a copy of ParamRecordRecord, except that the iteration is done over the index of the object array.
//The index keys are numbers or `${number}`, rather than strings coming from the BBB enumerator.
type ParamRecordArray<T> = {
    [KA in keyof T & AAA]: {
        [KB in keyof T[KA] & `${number}`]?: TestData<Extract<T[KA][KB], number> extends never ? number : Extract<T[KA][KB], number>>
    }
}

new class TestRecorArray<T> {
    constructor(data: ParamRecordArray<T>) { }
}(
    {
        [AAA.a1]: [
            {
                _: '',
                check: (c) => { }//🟢number
                //     ^?
            },
        ],
        [AAA.a2]: [
            {
                _: '',
                components: [1],
                check: (c) => { }//🔴1
                //     ^?
            },
            {
                _: '',
                components: [2],
                check: (c) => { }//🔴2
                //     ^?
            },
        ],
    }
)


//ok not work so let try debug ?
type debug = ParamRecordArray<{ [AAA.a1]: [ TestData<1>, TestData<2> ] }>
//   ^?
// ya it seem not able to extract the generic , but am not understand why !?

детская площадка

Заранее благодарю за разъяснения и решение, если это возможно.

ПРИМЕЧАНИЕ. Я не хочу использовать as const или function<const T>():T везде для каждой записи только для версий массива. const T в классе может быть решение, но, похоже, оно не работает!

Я также мог бы использовать одну обертку, но тип const T, похоже, в этом случае не обрабатывает вложенные Record<string, TestData[]>.

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

jcalz 01.09.2024 17:18

Ого, да, это именно то поведение, на которое я надеялся! Это увлекательно, спасибо за решение. В конце концов я смирился с тем, что везде использую константу! Это было невыносимо!

jon 01.09.2024 17:43

Напишу ответ, когда будет возможность.

jcalz 01.09.2024 18:22

Спасибо. Полагаю, это связано с тем, что иногда приходится разделять типы, потому что они могут вести себя по-разному. Коллега однажды сказал мне: «Иногда отображаемые типы второго уровня ведут себя иначе, чем типы первого уровня, и лучше выделить их в новый универсальный тип».

jon 01.09.2024 18:33

обратите внимание, что ваша строка отладки должна выглядеть как type debug = ParamRecordArray<{ [AAA.a1]: [1, 2] }>, а не type debug = ParamRecordArray<{ [AAA.a1]: [ TestData<1>, TestData<2> ] }>

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

Ответы 2

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

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

type ArrayElement<A> = A extends readonly (infer T)[] ? T : never;

type ParamRecordArray<T> = {
    [KA in keyof T & AAA]: T[KA] extends readonly any[] ? {
        [KB in keyof T[KA] & `${number}`]?: TestData<ArrayElement<T[KA]> extends number ? ArrayElement<T[KA]> : number>
    } : never
}

Спасибо за ваше предложение. К сожалению, похоже, это не работает, все превращается в тип any или может я что-то пропустил?

jon 01.09.2024 15:35

@jon Я проверил ссылку «игровая площадка» и ее сборку без каких-либо ошибок.

Hamdallah 01.09.2024 15:58

Представленная выше игровая площадка не содержит ошибок и успешно компилируется. Проблема заключается в наборе текста. Единственное нежизнеспособное решение — использовать as const везде. Поэтому ищу альтернативу этому вопросу. Ваше предложение не вызывает ошибок, но все типы становятся any и не закрепляют строки тегами 🔴. если вы поставите что-нибудь везде, да, приложение с компиляцией 😉

jon 01.09.2024 16:09
Ответ принят как подходящий

Вы столкнулись с давней ошибкой, о которой сообщалось по адресу microsoft/TypeScript#27995 , где отображаемые типы по типам массивов и кортежей не совсем ведут себя как гомоморфные отображаемые типы (см. Что означает «гомоморфный отображаемый тип» ? ), если только сопоставляемый тип не является параметром универсального типа. Если T является параметром универсального типа, то {[K in keyof T]: F<T[K]>} будет сопоставлять типы массивов с типами массивов, а типы кортежей с типами кортежей. Но в противном случае он будет отображать все ключи типа массива, включая такие вещи, как "length" и "push". Гомоморфные отображаемые типы позволяют выводить T из значения типа {[K in keyof T]: F<T[K]>}, и такого рода вывод — это то, что вам нужно для работы вашего кода. Этого не происходит, поэтому вы получаете неверный вывод.

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

type ParamRecordArray<T> = {
    [KA in keyof T & AAA]: F<T[KA]>
}

type F<T> = { [KB in keyof T]?: TestData<
    Extract<T[KB], number> extends never ? number : Extract<T[KB], number>
> }

Здесь F<T[KA]> делает то же, что и ваш исходный { [KB in keyof T[KA] & ${number}]?: ⋯ }. Обратите внимание, что нет смысла пересекать с `${number}`, поскольку все, что для этого нужно, — это нарушить гомоморфное отображение и предотвратить необходимый вывод, а гомоморфные отображаемые массивы/кортежи уже перебирают только числовые индексы.

Давайте убедимся, что это работает:

const z = new class TestRecorArray<T> {
    constructor(data: ParamRecordArray<T>) { }
}(
    {
        [AAA.a1]: [
            {
                _: '',
                check: (c) => { }//🟢number
                //     ^? (parameter) c: number
            },
        ],
        [AAA.a2]: [
            {
                _: '',
                components: [1],
                check: (c) => { }//🟢1
                //     ^? (parameter) c: 1
            },
            {
                _: '',
                components: [2],
                check: (c) => { }//🟢2
                //     ^? (parameter) c: 2
            },
        ],
    }
)

Выглядит хорошо!

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

Объяснение очень ясное, и я его понимаю. Спасибо за качество вашего времени, вы потрясающие.

jon 01.09.2024 21:14

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

Почему машинописный текст не позволяет частично указывать аргументы типа, чтобы создать новую универсальную функцию из другой универсальной функции?
Компонент React со свойством «as», которое указывает на другой компонент и также наследует его свойства
Как называется событие, которое передает только аргументы типа в общую функцию в машинописном тексте?
Инвертировать ключ типа T
Машинописный текст говорит, что собственность не существует
Как заставить TypeScript возвращать другое значение в зависимости от необязательного аргумента
Использование дженериков для сопоставления одного строкового литерала в Typescript
Что означает `T расширяет только чтение неизвестно [] | []` в сигнатуре функции TypeScript `Promise.all`?
Репликация агрегации проекта mongodb в Typescript
Могу ли я сделать вывод intellisense более читабельным для универсального Typescript с помощью Omit?