Я думаю, вы, возможно, немного переборщили с типами.Упрощение их действительно решит вашу проблему.
Давайте начнем с рассмотрения определения вашего типа для FieldParser
:
type FieldParser<F extends FieldKey> = (value?: FieldInputs[F]) => ParsedFields[F];
Все, что он действительно делает, это принимает значение и возвращает Validated
объект того же типа.Мы можем упростить это до:
type FieldParser<T> = (value?: T) => Validated<T>;
Это не только повышает сложность, но и значительно улучшает читаемость типа.
Обратите внимание, однако, что это означает, что мы утратили ограничение на FieldParser
, что оно может использоваться только с ключами от FieldKey
.Но на самом деле, если вы думаете об общей концепции «анализатора поля», она должна быть общей, и, как мы увидим через секунду, это не означает, что ваш код потребления становится менее строгим.
Мы также можем затем построить FieldParsers
как универсальный тип
type FieldParsers<T> = {
[K in keyof T]: FieldParser<K>;
}
Тогда оставшаяся часть кода может использовать те без проблем:
interface MyFieldInputs {
name: string;
price: number;
}
declare let fieldParsers: FieldParsers<MyFieldInputs>;
function update<T extends keyof MyFieldInputs>(field: T, value: MyFieldInputs[T]) {
const parser = fieldParsers[field];
parser.apply(value);
}
Однако мы можем сделать еще лучше,Вам все еще нужно использовать parser.apply(value)
здесь, когда на самом деле вы сможете просто вызвать parser(value)
.
Давайте сделаем дженерики еще на один шаг вперед, а не будем жестко кодировать функцию update
, чтобы использоватьконкретная переменная fieldParsers
, которую мы определили перед функцией, давайте использовать функцию для построения функции обновления.
function buildUpdate<TInputs>(parsers: FieldParsers<TInputs>) {
return function update<T extends keyof TInputs>(field: T, value: TInputs[T]) {
const parser = parsers[field];
parser(value);
}
}
Таким образом, мы можем легко связать все типы, и Typescript просто примет(и проверка типов) вызов parser(value)
.
Итак, собрав все это вместе, вы получите:
interface Validated<T> {
valid: boolean;
value: T;
}
/**
* Generic field validator
*/
type FieldParser<T> = (value?: T) => Validated<T>;
/**
* Generic set of field validators for a specific set of field types
*/
type FieldParsers<T> = {
[K in keyof T]: FieldParser<T[K]>
}
function buildUpdate<TInputs>(parsers: FieldParsers<TInputs>) {
return function update<T extends keyof TInputs>(field: T, value: TInputs[T]) {
const parser = parsers[field];
parser(value);
}
}
И вы воспользуетесь им, выполнив:
interface MyFieldInputs {
name: string;
price: number;
}
declare let fieldParsers: FieldParsers<MyFieldInputs>;
const update = buildUpdate(fieldParsers);
update('name', 'new name'); // Fully type checked
update('name', 5); // ERROR