如何以类型安全的形式使用 TypeScript 进行浅拷贝

how to do a shallow copy with TypeScript in a type safe form

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

第二行有类型问题:

Type '{}' is not assignable to type 'T'. 'T' could be instantiated with an arbitrary type which could be unrelated to '{}'.

我该如何解决?

方法一:Object.assign()
const shallowCopy = <T>(obj: T): T => {
  return Object.assign({}, obj);
}
方法二: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 属性。 See

此外,如果 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。但是,TypeScript 仍然会在 (1) 点抱怨,即使没有代码你有一段时间 a 不遵守定义。

解决方案是告诉 TS a 将是一个 Partial<Person> 这意味着它包含 Person 属性的一部分(或 none)(不是更多和不一样)。然后一旦我们完成赋值(并且我们确定我们分配了所有强制属性)我们(如果需要)强制 aPerson 以便下面的代码可以将 a 简单地视为Person(并假定 已设置 所有必需属性)。

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

在这个例子中,如果 T 是一个 POJO,在点 (1)newObj 符合类型 T(已经收到来自类型 T 对象)。 请注意,T 可能是一个 prototype 不同于 Object 的对象,因此 newObj 将不兼容 。即使对于 POJO,我也从未见过任何 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;
}

您可以将对象创建为 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

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

Playground

你不需要创建新的空对象并改变他。

如果您在 reduce 中没有任何额外的逻辑,您可能应该坚持使用 @crashmstr 的解决方案