Применять тип пути без дополнительных атрибутов

Я пишу вспомогательную функцию, которая устанавливает значение в объекте на path, вот так

function set<
    C extends Collection<any>,
    E = C extends Collection<infer U> ? U : never,
    K1 extends keyof E = keyof E,
    K2 extends keyof E[K1]= keyof E[K1]
    V extends E[K1][K2]= E[K1][K2]
>(
  collection: C, 
  entityId: string, 
  path: [K1, K2], 
  value: V
)

Это позволяет мне делать следующее

Collection.set(myCommentCollection, someComment.id, ['author', 'name'], 'jack');

Вышеупомянутое отлично работает, если ни атрибуты author, ни name не являются необязательными, и в этом случае компилятор с жалобой на

Argument of type '["author", string]' is not assignable to parameter of type '["author", never]'.

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

В рамках реализации функции я просто вернусь раньше, если полный путь не определен.

Это возможно? Спасибо

ОБНОВИТЬ

Если stats требуется, а атрибут total является необязательным, приведенное ниже не будет выполнено.

Collection.set(myCollection, someItem.id, ['stats', 'total'], 10)

Ошибка:

Argument of type '[number, "total"]' is not assignable to parameter of type '[never, "total"]'.
  Type 'number' is not assignable to type 'never'.

Вам просто это нужно максимум на 2 уровня?

Titian Cernicova-Dragomir 19.03.2018 16:15

Это упрощенная версия, у меня тоже есть чехлы для 1, 2 и 3 уровней

Tarlen 19.03.2018 16:16
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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
2
23
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Проблема в том, что для необязательных полей интерфейса тип поля будет TField|undefined. Для типов объединения, как обычно, у нас есть доступ только к общим полям, поэтому это означает, что ни одно из полей o TField не появится в keyof TField|undefined, поскольку этот TField не будет иметь ничего общего с undefined.

Мы можем использовать предопределенный условный тип, чтобы удалить undefined из типа поля:

function set<
    C extends Collection<any>,
    E = C extends Collection<infer U> ? U : never,
    K1 extends keyof E = keyof E,
    V1 = Exclude<E[K1], undefined>,
    K2 extends keyof V1= keyof V1,
    V2 extends V1[K2] =V1[K2]
>(
collection: C, 
entityId: string, 
path: [K1, K2], 
value: V2
) {}

//Usage 

interface Comment extends Entity {
    author?: {
        name?: string
    }
    stats : {
        total?: number
    }
}
export interface Collection<T extends Entity> extends Object {
    readonly entities: { [key: string]: T };
    readonly ids: EntityId[];
}
set(commentState, comment.id, ['author', 'name'], 'jack'); //OK
set(commentState, comment.id, ['stats', 'total'], 10); //OK

Это работает почти идеально, но не удастся, если только последний фрагмент пути является условным, т.е. приведенный ниже пример завершится ошибкой, если атрибут total является необязательным Collection.set(myCollection, itemId, ['stats', 'total'], 10).

Tarlen 19.03.2018 16:43

@Tarlen, не могли бы вы опубликовать более полный пример неудачи?

Titian Cernicova-Dragomir 19.03.2018 16:44

Обновленный пример

Tarlen 19.03.2018 16:46

@Tarlen Я не могу воспроизвести вашу ошибку, как вы ее описали. Я обновил свой тестовый код.

Titian Cernicova-Dragomir 19.03.2018 16:58

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

Tarlen 19.03.2018 16:59

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