#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;
}
Комментарии:
1. использовать любой тип-плохая практика в этом случае