как сделать неглубокую копию с помощью машинописного текста в типобезопасной форме

#javascript #typescript #shallow-copy

Вопрос:

 const shallowCopy = <T>(obj: T): T => {
  const newObj: T = {}; // type error here (ts2322)
  const objKeys = Object.keys(obj) as (keyof T)[];

  for (const k of objKeys) {
    newObj[k] = obj[k];
  }

  return newObj;
}
 

во второй строке проблема с типом:

Тип «{}» не может быть присвоен типу «T». «T» может быть создан с произвольным типом, который может быть не связан с» {}».

как мне это решить ?

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

1. какой тип проблемы?

2. В чем ошибка?

3. Тип «{}» не может быть присвоен типу «T». «T» может быть создан с произвольным типом, который может быть не связан с» {}».

4. T не имеет ограничений, и даже если вы укажете , что он расширяется Object , большинство T из них будут иметь требуемые свойства и {} не будут допустимым назначением. Что-нибудь не так с разрушением? Игровая <T extends Object>(obj: T): T => ({...obj}) площадка

Ответ №1:

Способ 1: Object.assign()
 const shallowCopy = <T>(obj: T): T => {
  return Object.assign({}, obj);
}
 
Способ 2: as

Используйте as , чтобы заставить TypeScript интерпретировать объект as утвержденного типа.

 const shallowCopy = <T>(obj: T): T => {
  const newObj: Partial<T> = {};
  for (const k of Object.keys(obj)) {
    newObj[k] = obj[k];
  }
  return newObj as T;
}
 

Или

 const shallowCopy = <T>(obj: T): T => {
  const newObj: T = {} as T;
  for (const k of Object.keys(obj)) {
    newObj[k] = obj[k];
  }
  return newObj;
}
 
Предупреждение

Вышеприведенные способы использования Object.keys() , следовательно, будут копировать только own enumerable свойства. Видеть

Кроме того, если T какой-либо экземпляр отличается от Object , prototype цепочка будет разорвана (но TS не обнаружит этого). Я предлагаю использовать эту функцию, которая сохраняет цепочку прототипов.

 const shallowCopy = <T>(obj: T): T => {
  const copy = Object.assign({}, obj);
  Object.setPrototypeOf(copy, Object.getPrototypeOf(obj));
  return copy;
}
 
Объяснение

Одна из основных задач TypeScript состоит в том, чтобы убедиться, что объекты всегда соответствуют объявленному типу (то, что не навязывается в JS). Когда у вас есть что-то вроде этого типа

 interface Person {
  name: string;
  height?: number; 
}

let a: Person = {};
// (1)
a.name = 'Time';
// (2)
 

В какой-то момент (1) объект a будет не соответствовать типу Person , так как в нем отсутствует name свойство. В какой-то момент (2) вы сделали a согласованным, так как теперь у него есть name свойство типа String . Но все равно, машинопись будет жаловаться, как в тот момент (1) , даже если у вас нет кода, у вас есть момент времени, когда a не соблюдается определение.

Решение состоит в том, чтобы сообщить TS, что a это будет a Partial<Person> , что означает, что оно содержит часть (или ничего) Person свойств (не больше и не отличается). Затем, как только мы закончим с назначением (и мы уверены, что назначили все обязательные свойства), мы (при необходимости) заставим a Person , чтобы следующий код мог обрабатываться a просто как Person (и предполагать, что заданы все обязательные свойства).

 let a: Partial<Person> = {};
a.name = 'Time';
return a as Person;
 

В этом примере, если T является POJO , в данный момент (1) newObj соответствует типу T (получив назначение всех ключей от T объекта типа). Обратите внимание, что это T может быть объект, prototype отличающийся от Object и, следовательно newObj , не будет соответствовать требованиям. Даже для POJOs я никогда не видел, чтобы какая-либо реализация TS (или IDE) обнаруживала этот шаблон, поэтому нам нужно добавить as T

 const shallowCopy = <T>(obj: T): T => {
  const newObj: Partial<T> = {};
  for (const k of Object.keys(obj)) {
    newObj[k] = obj[k];
  }
  // (1)
  return newObj as T;
}
 

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

1. да, компромисс с tsc

2. Да, это больше, чем компромисс, Т. С. знает только часть истории, вам нужно рассказать ему, что вы будете делать и что вы делали в некоторых случаях.

3. Текущее объявление статического типа TypeScript не может описать процесс динамического назначения, и мы должны были использовать as его или нет?

4. @eczn Я добавил объяснение, надеюсь, оно понятно

Ответ №2:

 const shallowCopy = <T extends Record<string, unknown>>(obj: T) =>
    Object.keys(obj).reduce((acc, elem) => ({
        ...acc,
        [elem]: acc[elem]
    }), {} as T)
 

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

Вам не нужно создавать новый пустой объект и мутировать его.

Если у вас нет никакой дополнительной логики внутри reduce , вам, вероятно, следует придерживаться @crashmstr этого решения

Ответ №3:

Вы можете создать объект как any тип, а затем привести его к T типу после копирования всех ключей:

 const shallowCopy = <T>(obj: T): T => {
  const newObj: any = {};
  const objKeys = Object.keys(obj) as (keyof T)[];

  for (const k of objKeys) {
    newObj[k] = obj[k];
  }

  return newObj as T;
}
 

poc

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

1. использовать любой тип-плохая практика в этом случае