Я создал функцию проверки объекта конфигурации, которая в основном проверяет соответствие объекта чертежу. Очень похоже на то, как работает React Prop Types. Я буду использовать это для автоматизации развертывания приложений на разных веб-сайтах с разными файлами конфигурации, чтобы убедиться, что файл конфигурации правильно определен перед попыткой развертывания.
Работает это так:
У меня есть функция, которая принимает объект и возвращает функцию.
Пример:
const blueprint = {
stringValue: ConfigTypes.string,
requirednumberValue: ConfigTypes.number.isRequired,
boolOrStringValue: ConfigTypes.oneOfType([ConfigTypes.string, ConfigType.bool])
} //The syntax here is very similar to that of React Prop Types. I am essentially defining what I expect my object to look like.
const checker = ConfigChecker(blueprint)
ConfigChecker
берет схему того, какие ключи мы ожидаем быть в объекте, какие типы значений для указанных ключей, и являются ли они необязательными или обязательными. ConfigChecker
возвращает функцию, которая принимает в качестве аргументов 2 объекта.
Пример:
const config = {
stringValue: "Hello"
boolOrStringValue: true
}
const defaults = {
requirednumberValue: 5,
boolOrStringValue: false
}
checker(config, defaults) //checker is defined in the above example. The return of ConfigChecker(blueprint)
Аргумент config
- это объект конфигурации, который мы планируем использовать для приложения, тогда как аргумент defaults
- это пары значений ключа по умолчанию, которые мы можем использовать для приложения, если они не указаны в объекте конфигурации.
Внутри аргумент config
и аргумент defaults
глубоко объединены вместе с объектом конфигурации, перезаписывая одинаковые значения ключей в объекте значений по умолчанию.
Таким образом, результатом приведенного выше примера будет:
{
stringValue: "Hello"
boolOrStringValue: true
requirednumberValue: 5,
}
После объединения двух аргументов они проверяются на blueprint
, чтобы убедиться, что конечный объект конфигурации, содержащий объединенные объекты default
и config
, определен правильно.
Файл объявления для этой функции выглядит так:
interface Requireable {}
interface ConfigType {
isRequired: Requireable;
}
type CType = {
/**
* A string value.
*/
string: ConfigType;
/**
* A boolean value.
*/
bool: ConfigType;
/**
* A number value.
*/
number: ConfigType;
/**
* A function.
*/
func: ConfigType;
/**
* An object. Not an array.
*/
object: ConfigType;
/**
* An array.
*/
array: ConfigType;
/**
* Any value.
*/
any: ConfigType;
/**
* An array of a specific type.
*/
arrayOf: (type: CType[keyof CType]) => ConfigType;
/**
* An object containing a specific type.
*/
objectOfType: (type: CType[keyof CType]) => ConfigType;
/**
* One of these values.
* @example
* OneOf(["Hello", "Goodbye", false])
*/
oneOf: (enums: Array<any>) => ConfigType;
/**
* One of these types.
* @example
* OneOf([ConfigType.string, ConfigType.number])
*/
oneOfType: (types: Array<CType[keyof CType]>) => ConfigType;
/**
* An object with specific keys and value types.
*/
objectOf: (obj: { [key: string]: CType[keyof CType] }) => ConfigType;
/**
* An object with specific keys and value types. The objects must strictly match.
*/
exactObjectOf: (obj: { [key: string]: CType[keyof CType] }) => ConfigType;
};
/**
* Check functions.
*/
export const ConfigTypes: CType;
type Id<T> = { [K in keyof T]: T[K] };
type SpreadProperties<L, R, K extends keyof L & keyof R> = {
[P in K]: L[P] | Exclude<R[P], undefined>;
};
type OptionalPropertyNames<T> = {
[K in keyof T]: undefined extends T[K] ? K : never;
}[keyof T];
type Spread<L, R> = Id<
// Properties in L that don't exist in R
Pick<L, Exclude<keyof L, keyof R>> &
// Properties in R with types that exclude undefined
Pick<R, Exclude<keyof R, OptionalPropertyNames<R>>> &
// Properties in R, with types that include undefined, that don't exist in L
Pick<R, Exclude<OptionalPropertyNames<R>, keyof L>> &
// Properties in R, with types that include undefined, that exist in L
SpreadProperties<L, R, OptionalPropertyNames<R> & keyof L>
>;
export default function <S extends { [key: string]: CType[keyof CType] }>(
schema: S
): <C extends { [key: string]: any }, D extends { [key: string]: any }>(
config: C,
defaults?: D
) => Pick<Spread<C, D>, keyof S>;
По сути, этот файл декларации заявляет, что результирующее значение вызова ConfigChecker(blueprint)(config, defaults)
должно быть объектом, содержащим только ключи внутри объекта blueprint
с типами значений этих ключей, поступающими из объединенных объектов config
и defaults
. Следовательно, если бы я добавил ключ к объекту config
без предварительного определения ключа в объекте blueprint
, возвращаемый объект не содержал бы этот добавленный ключ; в возвращаемом объекте будут определены только ключи внутри объекта blueprint
.
Хотя это хорошо работает, это просто обходной путь в отношении того, что я действительно пытаюсь сделать.
Чего я действительно хотел бы добиться от моего файла декларации, так это следующего:
Учитывая blueprint
выше, машинописный текст должен сделать вывод, что возвращаемый объект до предоставления объектов config
и defaults
должен выглядеть следующим образом:
{
stringValue?: string,
requirednumberValue: number,
boolOrStringValue?: boolean | string
}
Затем, после добавления объектов config
и defaults
, машинописный текст должен перекрестно ссылаться на них на чертеж и возвращать что-то вроде этого:
{
stringValue: string,
requirednumberValue: number,
boolOrStringValue: boolean
}
Итак, во-первых, Typescript должен сделать вывод, что тип возвращаемого значения функции должен быть объектом, содержащим необязательные и обязательные пары ключ / значение. Эти пары ключ / значение могут быть нескольких типов, как в случае с: boolOrStringValue?: boolean | string
, который является необязательным и может быть логическим или строковым.
Наконец, Typescript должен читать значения в объединенных объектах config
и defaults
и заменять предполагаемые типы на известные типы.
Я знаю, что первая работа работает нормально, однако, помимо того, что это классная функция, она также позволит мне сосредоточиться на Typescript, поскольку я только начал. Я люблю бросаться в ловушку, делая такие вещи. Можно ли что-то подобное сделать или я не в себе?
Заранее спасибо.