Условные типы для запроса/ответа машинописного текста

В настоящее время я играю с typescript@next, в котором теперь объединены PR условных типов.

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

loadUser({
    id: true,
    name: {
     first: true
    }
});

Когда тип User выглядит как

type User = {
    name: {
        first: string;
        last: string;
    };
    id: string;
}

и возвращаемое значение loadUser в этом случае будет соответствовать

{
    id: string;
    name: {
        first: string;
    }
}

Что я сделал до сих пор, так это следующее:

type DeepSelect<T> = {[K in keyof T]?: T[K] extends string ? boolean : DeepSelect<T[K]>};

type DeepPick<T, S extends DeepSelect<T>> = {[K in keyof S]: S[K] extends DeepSelect<infer U> ? DeepPick<T[K], U> : T[K]}

function loadUser<S extends DeepSelect<User>>(select: S): DeepPick<User, S> {
    ...
}

Проблема двоякая:

  1. Любое использование T[K] в ошибках определения DeepPick с type K cannot be used to index type T.

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

<удар>2. Последнее использование S[K] в ошибках определения DeepPick говорит о том, что type boolean | DeepSelect<T[K]> is not assignable to type DeepSelect<T[K]>

И здесь я чувствую, что, поскольку эта часть условия типа выполняется только в том случае, если S[K] не расширяет логическое значение, тогда он должен иметь возможность сделать вывод, что S[K] не boolean | DeepSelect<T[K]>, а вместо этого только DeepSelect<T[K]>

Я понимаю, что, поскольку этот PR был объединен только вчера, не многие люди будут иметь обширное представление об этих вопросах, но я был бы очень благодарен, если бы кто-нибудь помог мне понять, как я могу правильно структурировать эти типы.

Обновление 1

Хорошо, я думаю, что решил проблему № 2, используя, также новый, вывод типов. Я изменил определение типа DeepPick с:

type DeepPick<T, S extends DeepSelect<T>> = {[K in keyof S]: S[K] extends boolean ? T[K] : DeepPick<T[K], S[K]>}

to:

type DeepPick<T, S extends DeepSelect<T>> = {[K in keyof S]: S[K] extends DeepSelect<infer U> ? DeepPick<T[K], U> : T[K]}

person casieber    schedule 06.02.2018    source источник


Ответы (1)


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

type DeepSelect<T> = {
  [K in keyof T]? : T[K] extends object ? DeepSelect<T[K]> : true
};

type DeepPick<T, S> = {
  [K in keyof S & keyof T]: S[K] extends true ? T[K] : DeepPick<T[K], S[K]>
}

Я изменил их оба так, чтобы они обрабатывали только true вместо boolean, так как кажется, что вы на самом деле не собираетесь false сигнализировать о том, что свойство должно быть включено (а вы?).

Я также изменил DeepSelect на рекурсию вниз, если свойство является object, поэтому оно должно продолжать работать, если User имеет некоторые свойства, отличные от string (например, age: number?). Это делает его немного более общим.

Наконец, я снял ограничение на DeepPick, что S должен расширять DeepSelect<T>, и вместо сопоставления keyof S я сопоставляю keyof S & keyof T. Удаление ограничения гарантирует, что рекурсия будет работать, а пересечение ключей S и T гарантирует, что компилятор распознает, что оба T[K] и S[K] существуют. Вполне возможно, что в идеальном мире компилятор мог бы понять, что то, как вы написали, правильно, но я думаю, что мир не идеален.

Обратите внимание, что функция loadUser по-прежнему имеет ограничение на S:

declare function loadUser<S extends DeepSelect<User>>(
  select: S
): DeepPick<User, S>;

поэтому он будет работать по желанию:

const u = loadUser({
    id: true,
    name: {
     first: true
    }
});
u.id
u.name.first

Обратите внимание: как в вашем, так и в моем коде, похоже, нет дополнительные проверки свойств объекта, переданного loadUser(). Например:

const u = loadUser({
    ib: true, // no error, oops
    name: {
     first: true
    }
});
u.id // error 
u.name.first

Я не знаю, почему это так, или это ошибка, или ограничение, или что-то еще. Но, может быть, стоит иметь это в виду. А может и нет, если функция изменится до релиза. ???? Кто знает!

Удачи!

person jcalz    schedule 07.02.2018
comment
Спасибо! Мне нравится изменение с boolean на true, однако я заметил, что если вы введете его как true, вы не сможете создать объект select перед передачей его в loadUser, поскольку вместо этого он интерпретирует true в объекте как тип boolean из true, так что пока я могу придерживаться boolean. Но это все работает просто отлично. Спасибо еще раз. - person casieber; 07.02.2018