Компилятор, как правило, не может определить присваиваемость неразрешенных условных типов (то есть условных типов, которые не могут быть с нетерпением оценены, потому что хотя бы один из 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 Хорошо, надеюсь, это поможет.Удачи!