Делегировать определения типов для реализации - PullRequest
0 голосов
/ 29 марта 2020

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

{
   [key]: (errorMessage?: E) => Validation<T, E>
}

Например, у меня может быть что-то вроде этого.

interface StudentSchema {
  name: string;
  id: string;
  country: string;
  age: number;
}

interface Validation<T, E> {
  foo: string;
  bar: number;
  validations: (errorMessages? E) => SchemaValidation<T>;
}

interface SchemaValidation<T> {
   setIsRequired(message?: string) => SchemaValidation<T>;
   setMatches(regex: any, message?: string) => SchemaValidation<T>;
   setMax(max: number, message?: string) => SchemaValidation<T>;
   isValid (data: T) => boolean;
}

class StringSchemaValidation extends SchemaValidation<string> { /* ... */ }
class NumberSchemaValidation extends SchemaValidation<number> { /* ... */ }

const StudentValidations: Record<keyof StudentSchema, Validation<T, E>> = {
  name: {
    foo: 'AAA',
    bar: 1,
    validation: (errorMessages?: { required: string }): SchemaValidation<string> =>
      new SchemaValidation()
      .setIsRequired(errorMessages?.required),
  },
  id: {
    foo: 'BBB',
    bar: 2,
    validation: (errorMessages?: { required: string; match: string; max: (max: number) => string }): SchemaValidation<string> =>
       new SchemaValidation()
      .setIsRequired(errorMessages?.required)
      .setMatches(/[a-zA-Z0-9]+/, errorMessages?.match)
      .setMax(30, errorMessage?.max),
  },
  country: {
    foo: 'CCC',
    bar: 3,
    validation: (errorMessages?: { required: string }): SchemaValidation<string> =>
      new SchemaValidation()
      .setIsRequired(errorMessage?.required),
  },
  age: {
    foo: 'DDD',
    bar: 4,
    validation: (errorMessages?: { required: string }): SchemaValidation<number> =>
      new NumberSchemaValidation()
      .setIsRequired(errorMessage?.required),
  },
};

Прежде всего, это не компилируется, потому что я не указываю E для StudentValidations, но я не знаю, что поместить, потому что он отличается для каждой записи. Я могу использовать any или что-то подобное, но тогда я потеряю информацию о типе, когда получу доступ к функции validations.

Что я хочу, так это по существу "делегировать" объявление типа реализации, но это не представляется возможным. Есть ли способ добиться того, чего я хочу? Или, может быть, есть другой способ добиться этого?

1 Ответ

0 голосов
/ 30 марта 2020

Я думаю, здесь есть две проблемы. В Validation<T, E>, полученном из интерфейса, подобного StudentSchema, он имеет те же ключи, что и StudentSchema, и для каждого ключа K тип T является типом соответствующего свойства StudentSchema[K]. Эта часть идеально подходит для отображаемых типов . Это будет выглядеть примерно так:

/* type ValidationFor<T> = { [K in keyof T]: Validation<T[K], ...>}; */
/* type ValidationForStudentSchema = ValidationFor<StudentSchema>; */

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

type ValidationFor<T, E extends Record<keyof T, any>> = 
   { [ K in keyof T ]: Validation<T[K], E[K]> };

type ValidationForStudentSchema<E extends Record<keyof StudentSchema, any>> =
   ValidationFor<StudentSchema, E>  */

И тогда ваш пример StudentValidations будет иметь следующий тип:

const StudentValidations: ValidationFor<StudentSchema, {
    name: { required: string },
    country: { required: string; },
    age: { required: string; },
    id: { required: string; match: string; max: (max: number) => string; }
}> = ...;

Но для этого потребуется либо явно выписать это E тип объекта, или попытайтесь заставить компилятор выводить его, ни один из которых не особенно интересен.


Вместо этого я бы предложил ослабить ограничение на свойства E и используйте намеренно "все, что идет" тип any:

/* type ValidationFor<T> = { [K in keyof T]: Validation<T[K], any> }; */

Это работает; ValidationFor<StudentSchema> будет соответствовать желаемым значениям, но это слишком "забывчиво"; поскольку он будет использовать any вместо фактического E свойства, которое вы ему дадите. Чтобы помнить компилятор, мы создаем вспомогательную функцию generi c, которая просто принимает значение типа generi c, extends ValidationFor<StudentSchema>:

const valsForStudentSchema =
    <U extends { [K in keyof StudentSchema]: Validation<StudentSchema[K], any> }>(
        valMap: U
    ) => valMap;

Если Вы передаете аргумент valsForStudentSchema(), который не соответствует ValidationFor<StudentSchema>, вы получите ошибку. Если вы передадите аргумент, что соответствует , компилятор просто вернет его, не расширив его до ValidationFor<StudentSchema> и не забыв.

Вот так:

const StudentValidations = valsForStudentSchema({
    name: {
        foo: 'AAA',
        bar: 1,
        validation: (errorMessages?: { required: string }): SchemaValidation<string> =>
            new StringSchemaValidation()
                .setIsRequired(errorMessages?.required),
    },
    id: {
        foo: 'BBB',
        bar: 2,
        validation: (errorMessages?: { required: string; match: string; max: (max: number) => string }): SchemaValidation<string> =>
            new StringSchemaValidation()
                .setIsRequired(errorMessages?.required)
                .setMatches(/[a-zA-Z0-9]+/, errorMessages?.match)
                .setMax(30, errorMessages?.max(30)),
    },
    country: {
        foo: 'CCC',
        bar: 3,
        validation: (errorMessages?: { required: string }): SchemaValidation<string> =>
            new StringSchemaValidation()
                .setIsRequired(errorMessages?.required),
    },
    age: {
        foo: 'DDD',
        bar: 4,
        validation: (errorMessages?: { required: string }): SchemaValidation<number> =>
            new NumberSchemaValidation()
                .setIsRequired(errorMessages?.required),
    },
});

Обратите внимание, что вы спрашивали о наличии чего-то, что работает с другими схемами, а не только с StudentSchema. Мы можем сделать это с помощью curry , создав функцию generi c, которая принимает тип схемы и возвращает другую функцию generi c, которая создает ValidationFor такого типа:

const validationFor = <T>() => <U extends { [K in keyof T]: Validation<T[K], any> }>(
    valMap: U
) => valMap;

const StudentValidations = validationFor<StudentSchema>()({
    // ... put the same object here as before
});

И вы можете использовать его с другим типом, подобным этому:

interface Foo {
    a: boolean
}

const foo = validationFor<Foo>()({
    a: {
        foo: "",
        bar: 1,
        validation: (e?: { a: string }): SchemaValidation<boolean> =>
            new BooleanSchemaValidation()
    }
})

Хорошо, надеюсь, это поможет; удачи!

Детская площадка ссылка на код

...