Comments 8
Я вот так решил:
type ExtractFromObject<O, K> = [never[], K] extends [O, number | `${number}`]
? O[number & keyof O] | undefined
: K extends keyof O ? O[K] : undefined;
type GetWithArray<O, K> = O extends null | undefined
? O
: K extends readonly []
? O
: K extends readonly [infer Key, ...infer Rest]
? GetWithArray<ExtractFromObject<O, Key>, Rest>
: never;
У вас не совсем правильно работает на массивах, например, у массивов есть свойство 'length', которое должно быть number.
Выкидывать null и undefined можно с помощью типа NonNullable < T >, который под капотом просто T & {}. Почему так? Потому что {} - это "всё кроме null и undefined"
@AlexandroppolusОгонь решение ? Я проверил, что оно работает с TypeScript 4.4, когда я изначально писал статью, так что большой респект.
У вас не совсем правильно работает на массивах, например, у массивов есть свойство 'length'
Не очень понял, как это можно использовать.
Если предлагается использовать { length: number }
для отсеивания массивов от кортежей, есть пограничные случаи структур данных, в которых тоже есть свойство length, но они при этом не являются массивом/кортежем. Т.к. я работаю в видео стриминге, для меня самый простой пример - это TimeRanges
- https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges. Но это редкий случай, их 1%, поэтому в целом можно. Но я не стал так делать.
Выкидывать null и undefined можно с помощью типа NonNullable < T >, который под капотом просто T & {}
С NonNullable
и {}
я согласен. Уточнение, что {}
работает только с версии 4.8, а писал я статью в 2021 году, когда был 4.4, но все равно валидно.
Комментируя ваше решение:
Вынесение null | undefined
очень хорошая идея, т.к. я рассматривал ветки очень локально, не пытаясь найти общий знаменатель во всем решении.
В остальном: не хочу быть занудой, но условные типы в вашем решении свернуты, то есть если бы я свои свернул, получилось бы ваше решение.
[never[], K] extends [O, number | `${number}`]
Это сокращенная версия, или свертка, для never[] extends O ? K extends number | `${number}` ? ... : never : never
. Я старался избегать такие хаки, про это можно отдельную статью писать.
O[number & keyof O]
Это свертка для значений массива, что-то типа type NumericValues<O> = number extends keyof O ? O[number] : never;
, который потом вызвался NumericValues<O>
(тут number & keyof O
- свертка для условного типа в NumericValues
)
В целом, конечно, очень красиво и эффектно. Я допишу статью, как до этого дойти и добавлю пункт между 3. Получение пути из массива и кортежи и 4. Общее решение. Спасибо за идею ?
Не очень понял, как это можно использовать.
Ну, например, GetWithArray < ProductionObject, ['versions', 'length'] > должен быть number - берем длину массива.
Я тут слегка поправил своё решение, точнее ExtractFromObject
. В предыдущем были косяки с кортежами и массивами:
1) для кортежа ключ number возвращал все свои значения без undefined, а должен с ним.
2) неправильно работало с ключами-объединениями для массивов (см. примеры в extendedCases), в том числе never. Ключи never срезают всё кроме null и undefined, т.к. эти двое не индексируются.
3) для массива ключ never возвращал все его значения (думаю, это баг TS), а должен вернуть never, как в объектах
В дополнение, по ощущению, ваше решение работает быстрее, чем мое, т.к. в моем решении приходится обрабатывать очень много условных типов. Потестировал сегодня ночью свое решение в ts-essentials (README для PathValue) и нарвался на T2589: Type instantiation is excessively deep and possibly infinite
, когда добавляю StringPath extends Paths<Type>
для параметра path: StringPath
Можете кинуть ссылку на Playground с ошибкой?
Сорри, не сразу увидел вопрос, вот пример: https://tsplay.dev/WJ09kN
Правда я чекнул, что если взять ваше решение, то будет та же самая ошибка - https://tsplay.dev/wjL5Mm
Я уверен, что проблема где-то на поверхности, но не копал еще вглубь
Глянул код, не смог найти причину ошибки. Взял вариан с моим решением, и написал так, поменяв Paths, вроде попустило. Навскидку примерно всё то же самое, почему работает, непонятно. Есть дублирование кода в Paths, но не стал заморачиваться.
А что, если бы функция get в Lodash выводила типы за вас в TypeScript