Установка подмножества значений составных объектов с использованием ключей из другого массива в TypeScript

#typescript #loops #typescript-typings #typescript-generics

#typescript #циклы #typescript-типизации #typescript-generics

Вопрос:

Я знаю, что об этом спрашивали уже много раз, и я проверил все другие ответы — но все еще не удалось решить мою ситуацию, так что здесь…

У меня довольно сложная структура из-за внешних ограничений (форма, выбор входных данных, DB API).

Я получаю входные данные из пользовательской формы в свой объект. Одним из этих элементов является поле выбора с несколькими вариантами выбора, которое возвращает массив значений (дни недели). Он может возвращать переменное количество значений в виде массива: одно значение или даже семь значений.

Я хочу выполнить цикл по этим значениям массива и использовать их в качестве ключей для установки логического значения моего другого объекта.

С этой попыткой # 1 TypeScript не компилируется и жалуется, что:

 Type 'true' is not assignable to type 'undefined'.(2322)
  

С попыткой # 2 Typescript не компилируется, потому что:

 Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'IDayOfWeek'.
  No index signature with a parameter of type 'string' was found on type 'IDayOfWeek'.(7053)
  

Сначала я намеревался сообщить Typescript, что все эти значения являются boolean . Я пытался фильтровать ключи, чтобы принимать только логические значения, но не справился, затем я попытался поиграть с реализацией некоторых идей, которые я нашел здесь, но пока у меня ничего не получалось, или я не смог реализовать это из-за ограничений.

Раньше в JS это было легко, но в Typescript … ну что ж … 😉

Я был бы признателен за любую помощь, спасибо!

 // I can't change this:
interface IDayOfWeek {
  sunday?: boolean;
  monday?: boolean;
  tuesday?: boolean;
  wednesday?: boolean;
  thursday?: boolean;
  friday?: boolean;
  saturday?: boolean;
  someOtherValue?: string;
  andAnother?: Date;
}

// I am obligate to use this Select structure:
interface ISelectOption<T> {
    label: string;
    value: T;
}

// I have a limited control of this (React form is mapped to this object with a hook):
interface IUserInput {
  someInputName: string;
  weekday: Array<ISelectOption<keyof IDayOfWeek>>;
}


// and here's where I'm stuck:
function saveUserInput(inp: IUserInput) {
  let dayOfWeek: IDayOfWeek = {};

  // Attempt #1
  for(let key in inp.weekday) {
    dayOfWeek[inp.weekday[key].value] = true;
  }

  // Attempt #2
  for(let key in dayOfWeek) {
      if(inp.weekday.hasOwnProperty(key)) {
          dayOfWeek[key] = true;
      }
  }
}
  

Игровая площадка

Ответ №1:

Это вполне возможно сделать без изменений IDayOfWeek .

Проблема в том, что keyof IDayOfWeek IUserInput ) недостаточно конкретен, т. Е. Мы получаем дни недели, которые имеют логические значения, но мы также получаем someOtherValue значения, которые не являются логическими. Таким образом, мы не знаем, правильный ли у нас тип, когда мы назначаем true .

Решение состоит в том, чтобы использовать только ключи, которые имеют логические значения, поэтому один из вариантов — просто вручную вывести их список:

 interface IUserInput {
  someInputName: string;
  weekday: Array<ISelectOption<'sunday' | 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday'>>;
}
  

Ссылка на игровую площадку

Другой, более популярный вариант — использовать вспомогательный тип для автоматического получения ключей, соответствующих логическому значению:

 // Helper to only get the keys that match a particular value.
type FilterKeys<T, U> = NonNullable<{ [K in keyof T]: T[K] extends (U | undefined) ? K : never }[keyof T]>;
  
 interface IUserInput {
  someInputName: string;
  weekday: Array<ISelectOption<FilterKeys<IDayOfWeek, boolean>>>;
}
  

Ссылка на игровую площадку

Комментарии:

1. Это именно то, что мне было нужно! Когда я попробовал что-то подобное — он пожаловался, потому что пытался установить значение типа «никогда». Я думаю, что это ваше добавление NonNullable исправляет эту часть. Спасибо!

2. Да, определенно, мне пришлось немного повозиться с вспомогательным типом, а не с наиболее читаемым кодом. NP

Ответ №2:

Мне удалось исправить ситуацию, чтобы она работала так, как, я думаю, вы предполагаете, чтобы они работали, смотрите Код ниже или эту ссылку на игровую площадку.

Единственное, что не работает, — это наличие someOtherValue и andAnother в IDayOfWeek интерфейсе, потому что они не имеют логического типа, поэтому попытка присвоить логическое значение любому из них приводит к ошибке типа.

Кроме этого, с несколькими изменениями из вашего кода все компилируется

 interface IDayOfWeek {
  sunday?: boolean;
  monday?: boolean;
  tuesday?: boolean;
  wednesday?: boolean;
  thursday?: boolean;
  friday?: boolean;
  saturday?: boolean;

  // These ⬇ won't work if you're assigning boolean values ❌
  // someOtherValue?: string;
  // andAnother?: Date;
}

interface ISelectOption<T> {
  label: string;
  value: T;
}

interface IUserInput {
  someInputName: string;
  weekday: ISelectOption<keyof IDayOfWeek>[]; // Note the use of keyof, not typeof
}


function saveUserInput(inp: IUserInput) {
  let dayOfWeek: IDayOfWeek = {};

  // This works ✅ as long as IDayOfWeek doesn't have any keys with non-boolean values!
  for (const key of inp.weekday) {
    dayOfWeek[key.value] = true;
  }
}
  

Комментарии:

1. Я знаю об этом 🙂 но, к сожалению, я не могу изменить эту часть. Оно поступает из внешнего источника.

2. Почему вы пытаетесь присвоить логические значения не булевым полям?