Определение типа для сравнения (и обновления) определенных полей объектов в Typescript

#typescript #typescript-generics #typing

#typescript #typescript-generics #ввод

Вопрос:

Я хотел бы сравнить и назначить определенные поля двух объектов, скажем:

 const left = { a: 1, ignored: 5, something: 7 };
const right = { a: 2, ignored: 6, else: 8, id: 18 };
  

И я хочу вызвать leftToRight(left, right, ['a']) , после чего right должно быть:

 { a: 1, ignored: 6, id: 18 }
  

и я хочу вызвать какую-то другую функцию и передать right.id , который, как я знаю, существует во втором аргументе.

Мой текущий подход:

 leftToRight(left, right, keys) {
  let changed = false;
  
  for (const key of keys) {
    if (!Object.is(left[key], right[key])) {
      right[key] = left[key];
      changed = true;
    }
  }

  doSomething(right.id)

  return changed
}
  

Я изо всех сил пытаюсь найти подходящее определение типа:-(

Первоначальный подход:

 leftToRight<T>(left: T, right: T, keys: Array<keyof T>): boolean
  

приводит к: «Свойство ‘id’ не существует для типа ‘T'», и я не нашел способа проверить это ( 'id' in right )

Вторая попытка:

 leftToRight<T>(left: T, right: T amp; {id: number}, keys: Array<keyof T>): boolean
  

приводит к тому, что «Тип ‘T’ не может быть присвоен типу ‘{ id: number; }'» для right[key] = left[key]

Третья попытка:

 leftToRight<T, U extends {id: number}>(left: T, right: U, keys: Array<keyof T amp; keyof U>): boolean
  

снова приводит к ошибке присвоения right[key] = left[key] , поскольку типы T и U могут быть совершенно не связаны.

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

1. id Должно ли поле быть a string или a number ?

2. Я исправил вопрос. Это число.

3. Я бы использовал leftToRight<T>(left: T, right: T amp; {id: number}, keys: Array<keyof T>): boolean и внутри реализации do (right as T)[key] = left[key] или что-то подобное, чтобы избавиться от этой ошибки; Я ищу, почему у компилятора возникла проблема с присвоением T[keyof T] (T amp; {id: number})[keyof T]) , но я пока ничего не нашел.

Ответ №1:

Редактировать:

Вы уточнили свои требования, чтобы сказать, что Left и Right у каждого могут быть свойства, которых нет в другом. Для обработки этого сценария нам нужны два общих типа.

Right это объект, который включает в себя id . Мы хотим, чтобы выбранные ключи присутствовали в обоих Left , Right и мы хотим, чтобы они имели одинаковые типы значений. Я определил generic Keys как любое подмножество ключей Right . Я определил Left как подмножество Right , содержащее все эти ключи.

 function leftToRight<Right extends {id: number}, Keys extends keyof Right>(
  left: Pick<Right, Keys>, right: Right, keys: Keys[]
) {
  let changed = false;
  
  for (const key of keys) {
    if (!Object.is(left[key], right[key])) {
      right[key] = left[key];
      changed = true;
    }
  }

  doSomething(right.id)

  return changed
}
  

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

Оригинальный ответ:

Я попробовал несколько вещей, прежде чем нашел тот, который работает. Конечно, это не самое чистое определение. Я получил ошибки при использовании generic для описания Left и добавления id в get Right , но удаления из Right works.

Мы говорим, что Right это объект со {id: number} свойством и Left должен иметь все свойства Right , кроме for id . Для того, чтобы элементы of keys присутствовали в обоих объектах, нам нужно игнорировать ключ id from keyof Right .

 function leftToRight<Right extends {id: number}>(
  left: Omit<Right, 'id'>, right: Right, keys: (Exclude<keyof Right, 'id'>)[]
) {
  let changed = false;
  
  for (const key of keys) {
    if (!Object.is(left[key], right[key])) {
      right[key] = left[key];
      changed = true;
    }
  }

  doSomething(right.id)

  return changed
}
  

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

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

1. Спасибо за ваше решение. Я не был достаточно ясен в своем вопросе (добавлю его): left не обязательно обладает всеми свойствами right (и наоборот).

2. Хорошо, так right что всегда имеет id , left и right имеет какие-либо свойства, и keys должны быть только общие свойства, это правильно? Нам понадобятся (как минимум) два обобщения, но я думаю, что смогу это сделать. Я посмотрю…

Ответ №2:

Попробуйте с left: Partial<T> помощью и подтвердите, что left[key] это не определено с ! помощью .

Пример:

 const left = { a: 1, ignored: 5};
const right = { a: 2, ignored: 6, id: 18 };

function leftToRight<T>(left: Partial<T>, right: T, keys: Array<keyof T>) {
  let changed = false;
  
  for (const key of keys) {
    if (!Object.is(left[key], right[key])) {
      right[key] = left[key]!;
      changed = true;
    }
  }

  // doSomething(right.id)

  return changed
}
  

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

1. Похоже, это сработает, если я использую: function leftToRight<T extends {id: number}>(left: Partial<T>, right: T, keys: Array<keyof T>) . Есть идеи, как избежать ненулевого утверждения?

2. Не уверен, каковы ваши мотивы для избежания ненулевого утверждения, поэтому я не знаю, лучше ли это, но вы могли бы использовать left as T . Пример: right[key] = (left as T)[key]

3. Я бы хотел избежать жалоб от моего linter 😉 и ситуаций, когда во время выполнения обнаруживаются ошибки, которые могли быть обнаружены во время компиляции (в режиме строгой проверки нуля). В этом случае left[key] это не определено, и, как следствие, right [key] будет неопределенным, что может привести к проблемам, если последующий код этого не ожидает. Однако это, вероятно, академическая проблема. Итак, спасибо за ваш ответ. Он делает то, что должен 🙂