Проблема при использовании дистрибутивных условных типов в сочетании с универсальным методом - PullRequest
1 голос
/ 06 мая 2019

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

Я использовал https://www.typescriptlang.org/docs/handbook/advanced-types.html в качестве примера (раздел: Распределительные условные типы)

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

Это неуниверсальная версия:

export type TypedPropertyNames<T, P> = { [K in keyof T]: T[K] extends P ? K : never }[keyof T];
export type StringPropertyNames<T> = TypedPropertyNames<T, string>;

interface Test {
  test: string;
}

function non_generic(form: Test, field: StringPropertyNames<Test>): string {
  return form[field];
}

Это работает.

Теперь, когда я изменю интерфейс Test на универсальный аргумент, он больше не будет компилироваться.

export type TypedPropertyNames<T, P> = { [K in keyof T]: T[K] extends P ? K : never }[keyof T];
export type StringPropertyNames<T> = TypedPropertyNames<T, string>;

function generic<T>(form: T, field: StringPropertyNames<T>): string {
  return form[field]; // This won't compile
}

Это ожидаемое поведение? Или это ошибка машинописи? Может кто-нибудь указать мне, как заставить работать универсальную версию (без каких-либо взломов)

Обновление 1:

Ошибка компиляции:

Type 'T[{ [K in keyof T]: T[K] extends string ? K : never; }[keyof T]]' is not assignable to type 'string'.

Ссылка на игровую площадку

Ответы [ 2 ]

2 голосов
/ 07 мая 2019

Компилятор, как правило, не может определить присваиваемость неразрешенных условных типов (то есть условных типов, которые не могут быть с нетерпением оценены, потому что хотя бы один из T или U в T extends U ? V : W еще не полностью указан).Это скорее ограничение дизайна, чем ошибка;компилятор не будет таким же умным, как человек (примечание к себе: вернитесь сюда, когда произойдет восстание машины, и отредактируйте это), поэтому мы не должны ожидать, что он просто «заметит», что T[TypedPropertyName<T,P>] extends P всегда должно быть верным,Мы могли бы написать определенный эвристический алгоритм, чтобы обнаружить ситуацию и выполнить желаемое сокращение, но он должен был бы иметь возможность быстро запускать , чтобы он не ухудшал время компиляции в 99% случаев.когда это было бы бесполезно.

Может кто-нибудь подсказать мне, как заставить работать универсальную версию (без каких-либо взломов)

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

function generic<T>(form: T, field: StringPropertyNames<T>): string {
  return form[field] as any as string;  // I'm smarter than the compiler ?
}

Или вы можете попытаться провести компилятор через шаги, необходимые для понимания того, что вы делаете безопасно.В частности, компилятор понимает , что Record<K, V>[K] присваивается V (где Record<K, V> определено в стандартной библиотеке как сопоставленный тип, ключи которого находятся в K и чьи значения в V).Поэтому вы можете ограничить тип T следующим образом:

function generic<T extends Record<StringPropertyNames<T>, string>>(
  form: T,
  field: StringPropertyNames<T>
): string {
  return form[field]; // okay
}

Теперь компилятор доволен.И ограничение T extends Record<StringPropertyNames<T>, string> на самом деле вовсе не является ограничением, поскольку ему будет соответствовать любой тип объекта (например, {a: string, b: number} extends Record<'a', string>).Таким образом, вы должны иметь возможность использовать его везде, где вы используете исходное определение (для конкретных типов T в любом случае):

interface Foo {
  a: string;
  b: number;
  c: boolean;
  d: "d";
}
declare const foo: Foo;
generic(foo, "a"); // okay
generic(foo, "d"); // okay

Это хаки?Hope Хорошо, надеюсь, это поможет.Удачи!

1 голос
/ 07 мая 2019

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

function generic<T>(form: T, field: StringPropertyNames<T>) {
  return form[field];
}

и даже правильно вводит возвращаемое значение в виде строки:

const test = {
  a: "b",
  c: 1,
  "other": "blah"
}
generic(test, "a").charAt(0) //passes - "b"
generic(test, "a") * 5 // fails - function result is not a number
generic(test, "c") //fails - "c" is not assignable to "a" | "other"

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

function generic<T extends object>(form: T, field: StringPropertyNames<T>) {
  return form[field];
}
...