TypeScript: защита типа аргумента на основе предыдущего

Я хочу реализовать проверку типов аргументов функции, где свойства второго аргумента основаны на свойствах предыдущего.

Переменная 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 определен в другом месте и не знает о переменной values
  • customFunction используется в нескольких местах приложения, поэтому передача config как литерала объекта нецелесообразна.

Любая помощь?

Боковое примечание: ваши общие типы, похоже, вообще не заботятся о типах значений свойств в T. Если это правда, вам лучше сделать их универсальными в ключах (например, type CustomType <K extends keyof any> = { [P in K]: number }; или, точнее, Record<K, number>. Тогда ваша функция будет универсальной в K, где T при необходимости заменяется на Record<K, any>.

jcalz 16.05.2018 15:40

Он действительно не рассматривает типы значений в T. Но в вашем первом примере я получаю сообщение об ошибке Type 'T' does not satisfy the constraint 'string'.

crazko 16.05.2018 17:26

Имеет ли смысл это?

jcalz 16.05.2018 17:40

Это почти именно то, чего я хочу достичь! Но теперь имена свойств «значений» зависят от имен свойств config - я бы предпочел, чтобы это было наоборот - имена свойств config должны зависеть от имен свойств values.

crazko 16.05.2018 18:15
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
1
4
266
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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); 

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

Спасибо за ответ, я не знал о разнице между прямой передачей переменной и литерала объекта.

crazko 16.05.2018 17:12

К сожалению, я не уточнил свои текущие условия полностью: - config определен в другом месте и не знает о переменной values (хотя я импортирую интерфейс, поэтому я использовал его внизу). Итак, ваш первый вариант не сработает - я использую customFunction в нескольких местах приложения, поэтому передача config как литерала объекта нецелесообразна. Вот почему я хочу сохранить его в переменной. Что касается вашего третьего варианта - я не понимаю, как его применить в моем случае.

crazko 16.05.2018 17:19

@crazko Что вас сбивает с толку, выбрав третий вариант? Параметр config должен иметь аргумент универсального типа, и вам нужен дополнительный параметр, который всегда передается как значение true, но тип которого будет изменяться в зависимости от того, являются ли типы точным соответствием или нет. Если совпадение неточное, тип не будет истинным, и, следовательно, вызов вызовет ошибку.

Titian Cernicova-Dragomir 16.05.2018 17:27

Ах, теперь я понял. С учетом этого необходимо включить все свойства values в config (хотя я хочу, чтобы они были необязательными). Кроме того, свойство wrong по-прежнему может находиться в config. ... Или, возможно, я использую его неправильно и мне нужно больше с ним поиграть.

crazko 16.05.2018 17:37

@crazko Нет, свойства могут оставаться необязательными, просто у идентификатора config есть дополнительные свойства, вы получите сообщение об ошибке. Я добавил ссылку на игровую площадку, чтобы продемонстрировать поведение. Ошибка будет не в параметре config, а скорее в параметре validation.

Titian Cernicova-Dragomir 16.05.2018 17:41

Я попытался вставить foo, bar, wrong в config и не вижу ошибки. Также при наличии только foo возникает ошибка.

crazko 16.05.2018 18:21

@crazko, извините, в коде была ошибка, не подключился ли NoExtraProperties<TConfig, T>, не могли бы вы проверить сейчас?

Titian Cernicova-Dragomir 16.05.2018 18:29

Нет проблем, я очень ценю вашу помощь! Теперь он работает именно так, как ожидалось. Мне нужно пройти через это, чтобы правильно понять, что происходит. Еще раз спасибо!

crazko 16.05.2018 18:33

Другие вопросы по теме