Я хочу реализовать проверку типов аргументов функции, где свойства второго аргумента основаны на свойствах предыдущего.
Переменная config должна содержать только те свойства, которые присутствуют в объектах в массиве values. Свойства должны быть необязательными (необязательно иметь их все в config, но нельзя добавлять другие.
Пожалуйста, посмотрите пример кода:
type CustomType <T> = {
[K in keyof T]: number
};
type Config <T> = {
[K in keyof T]? : {
highPriority: boolean;
callback: (values: any[]) => number[];
}
};
const customFunction = <T>(values: T[], config: Config <T> ): Array <CustomType<T>> => {
// logic...
return [];
};
const values = [
{
foo: 'foo',
bar: 'bar'
},
{
foo: 'foo',
bar: 'bar'
}
];
// Should optionaly contain only "foo", "bar" properties in this example
const config = {
foo: {
highPriority: true,
callback: () => []
},
// not present in values objects
wrong: {
highPriority: true,
callback: () => []
}
};
// config should be marked with errors as "wrong" is not present in values objects
const result = customFunction(values, config);В последней строке config следует пометить как ошибку, поскольку он вводит свойство wrong, которого нет в исходном объекте values.
Я могу принудительно проверить реализацию интерфейса для config, но я думаю, что это не нужно и что это можно сделать и без него.
interface ISpecific {
foo: any,
bar: any
}
const values: ISpecific[] = [
{
foo: 'foo',
bar: 'bar'
},
{
foo: 'foo',
bar: 'bar'
}
];
const config: Config<ISpecific> = {
// ...
// wrong property is marked as error
}ОБНОВЛЕНО:
config определен в другом месте и не знает о переменной valuescustomFunction используется в нескольких местах приложения, поэтому передача config как литерала объекта нецелесообразна.Любая помощь?
Он действительно не рассматривает типы значений в T. Но в вашем первом примере я получаю сообщение об ошибке Type 'T' does not satisfy the constraint 'string'.
Имеет ли смысл это?
Это почти именно то, чего я хочу достичь! Но теперь имена свойств «значений» зависят от имен свойств config - я бы предпочел, чтобы это было наоборот - имена свойств config должны зависеть от имен свойств values.






Typescript проверяет наличие лишних свойств только в том случае, если вы назначаете литерал объекта непосредственно переменной / параметру данного типа, как описано в здесь. В вашем случае вы можете явно ввести config или использовать литерал объекта непосредственно в качестве аргумента:
const values = [
{
foo: 'foo',
bar: 'bar'
},
{
foo: 'foo',
bar: 'bar'
}
];
// We type relative to values, no need for the extra interface
const config: Config<typeof values[number]> = {
foo: {
highPriority: true,
callback: () => []
},
// will be an error
wrong: {
highPriority: true,
callback: () => []
}
};
//Or pass the object literal directly
const result2 = customFunction(values, {
foo: {
highPriority: true,
callback: () => []
},
// error
wrong: {
highPriority: true,
callback: () => []
}
});
Другой вариант - использовать условные типы и дополнительный параметр, чтобы вызвать ошибку, если переданный тип имеет дополнительные свойства:
type NoExtraProperties<TSource, TTarget> = Exclude<keyof TSource, keyof TTarget> extends never ? true : "Extra properties detected";
const customFunction = <T, TConfig extends Config<T>>(values: T[], config: TConfig, validate: NoExtraProperties<TConfig, T>): Array<CustomType<T>> => {
// logic...
return [];
};
// Argument of type 'true' is not assignable to parameter of type '"Extra properties detected"'.
const result = customFunction(values, config, true);
Детская площадка ссылка на сайт
Спасибо за ответ, я не знал о разнице между прямой передачей переменной и литерала объекта.
К сожалению, я не уточнил свои текущие условия полностью: - config определен в другом месте и не знает о переменной values (хотя я импортирую интерфейс, поэтому я использовал его внизу). Итак, ваш первый вариант не сработает - я использую customFunction в нескольких местах приложения, поэтому передача config как литерала объекта нецелесообразна. Вот почему я хочу сохранить его в переменной. Что касается вашего третьего варианта - я не понимаю, как его применить в моем случае.
@crazko Что вас сбивает с толку, выбрав третий вариант? Параметр config должен иметь аргумент универсального типа, и вам нужен дополнительный параметр, который всегда передается как значение true, но тип которого будет изменяться в зависимости от того, являются ли типы точным соответствием или нет. Если совпадение неточное, тип не будет истинным, и, следовательно, вызов вызовет ошибку.
Ах, теперь я понял. С учетом этого необходимо включить все свойства values в config (хотя я хочу, чтобы они были необязательными). Кроме того, свойство wrong по-прежнему может находиться в config. ... Или, возможно, я использую его неправильно и мне нужно больше с ним поиграть.
@crazko Нет, свойства могут оставаться необязательными, просто у идентификатора config есть дополнительные свойства, вы получите сообщение об ошибке. Я добавил ссылку на игровую площадку, чтобы продемонстрировать поведение. Ошибка будет не в параметре config, а скорее в параметре validation.
Я попытался вставить foo, bar, wrong в config и не вижу ошибки. Также при наличии только foo возникает ошибка.
@crazko, извините, в коде была ошибка, не подключился ли NoExtraProperties<TConfig, T>, не могли бы вы проверить сейчас?
Нет проблем, я очень ценю вашу помощь! Теперь он работает именно так, как ожидалось. Мне нужно пройти через это, чтобы правильно понять, что происходит. Еще раз спасибо!
Боковое примечание: ваши общие типы, похоже, вообще не заботятся о типах значений свойств в
T. Если это правда, вам лучше сделать их универсальными в ключах (например,type CustomType <K extends keyof any> = { [P in K]: number };или, точнее,Record<K, number>. Тогда ваша функция будет универсальной вK, гдеTпри необходимости заменяется наRecord<K, any>.