我正在try 将属性值从源S复制到目标T.在以编程方式判断该字段是否存在后,我希望TS允许我访问它,但不会:

● Test suite failed to run

    src/util/Copy.ts:18:18 - error TS2536: Type 'keyof T' cannot be used to index type 'S'.

    18           return source[field] === target[field];
                        ~~~~~~~~~~~~~
    src/util/Copy.ts:33:11 - error TS2536: Type 'keyof S' cannot be used to index type 'T'.

    33           match[field] = source[field];
                 ~~~~~~~~~~~~

我可以使源从目标扩展到只接受这两种类型的字段.但我的实用程序方法应该处理完全不同的类型.以下是失败的代码

export function copyFieldsWhenMatching<S extends object, T extends object>(
  sourceArray: S[],
  targetArray: T[],
  equalFields: (keyof T)[],
  fieldsToCopy: (keyof S)[],
  allowMultipleOccurrences: boolean
): T[] {
  const toReturn = [...targetArray];

  for (const source of sourceArray) {
    // Find matches based on equalFields
    const matches = toReturn.filter((target) => {
      return equalFields.every((field) => {
        // eslint-disable-next-line no-prototype-builtins
        if (source.hasOwnProperty(field) && target.hasOwnProperty(field)) {
          return source[field] === target[field];
        }
        return true; // Property doesn't exist in either source or target; continue matching.
      });
    });

    if (!allowMultipleOccurrences && matches.length > 1) {
      throw new DuplicateMatchesError(
        "More than one match found for the source object.",
        matches
      );
    } else if (matches.length >= 1 || allowMultipleOccurrences) {
      // Copy fields from source to matches
      for (const match of matches) {
        for (const field of fieldsToCopy) {
          match[field] = source[field];
        }
      }
    }
  }

  return toReturn;
}

这就是我如何破解它以通过(将目标投射到源,反之只是为了通过).这就把我带到了标题中的问题:

有没有一种更干净的方法来更新keyof T的价值?

import { DuplicateMatchesError } from "../errors";

export function copyFieldsWhenMatching<S extends object, T extends object>(
  sourceArray: S[],
  targetArray: T[],
  equalFields: (keyof T)[],
  fieldsToCopy: (keyof S)[],
  allowMultipleOccurrences: boolean,
): T[] {
  const toReturn = [...targetArray];

  for (const source of sourceArray) {
    // Find matches based on equalFields
    const matches = toReturn.filter((target) => {
      return equalFields.every((field) => {
        // eslint-disable-next-line no-prototype-builtins
        if (source.hasOwnProperty(field) && target.hasOwnProperty(field)) {
          return (
            // dirty assertion to hack TS
            (source as unknown as T)[field] ===
            target[field]
          );
        }
        return true; // Property doesn't exist in either source or target; continue matching.
      });
    });

    if (!allowMultipleOccurrences && matches.length > 1) {
      throw new DuplicateMatchesError(
        "More than one match found for the source object.",
        matches,
      );
    } else if (matches.length >= 1 || allowMultipleOccurrences) {
      // Copy fields from source to matches
      for (const match of matches) {
        for (const field of fieldsToCopy) {
          // dirty assertion to hack TS
          (match as unknown as S)[field] = source[field];
        }
      }
    }
  }

  return toReturn;
}

推荐答案

if (source.hasOwnProperty(field)) {
  return source[field] === target[field];
}

在您的示例代码中,您使用hasOwnProperty()来判断source是否具有该属性.但是,hasOwnProperty()不会缩小其目标范围,因此TS不会将source缩小到包括field指定的属性.

要解决此问题,可以创建一个调用hasOwnProperty()并缩小目标对象范围的user-defined type guard.

在某些情况下,TypeScrip会缩小变量类型的范围,例如使用typeof时.通过使用名为type predicate的返回类型,我们可以创建functions来缩小它们的输入类型.这些被称为user-defined type guards.

下面是hasOwnProperty()分中的1分:

function hasOwnProperty<O extends object, K extends PropertyKey>(
  obj: O,
  key: K,
): obj is O & Record<K, unknown> {
  return Object.prototype.hasOwnProperty.call(obj, key)
}

然后您可以这样使用它:

if (hasOwnProperty(source, field)) {
  return source[field] === target[field]
}

注意:这是用来与字符串键一起使用的.由于field被类型化为keyof T,因此它实际上将source类型化为类型S & Record<keyof T, unknown>(即,具有T的所有属性).但是,因为只需使用相同的变量(field)来索引source,所以没有问题.但如果你做得更多,要小心.

下面是放入您代码中的代码:TypeScript Playground.


欲了解更多有关类型卫士的信息,请参阅TS docs和优秀的TypeScript Deep Dive by basarat.

Typescript相关问答推荐

如何在类型脚本中声明对象其键名称不同但类型相同?

如何使用axios在前端显示错误体

我用相同的Redux—Toolkit查询同时呈现两个不同的组件,但使用不同的参数

动态调用类型化函数

如何根据另一个属性的布尔值来缩小函数属性的返回类型?

为什么从数组中删除一个值会删除打字错误?

如何设置绝对路径和相对路径的类型?

TypeScript:在作为参数传递给另一个函数的记录中对函数的参数实现类型约束

使某些(嵌套)属性成为可选属性

如何在另一个参数类型中获取一个参数字段的类型?

在实现自定义主题后,Angular 不会更新视图

在Typescript 中,有没有`index=unfined的速记?未定义:某个数组[索引]`?

如何将v-for中的迭代器编写为v-if中的字符串?

使用2个派生类作为WeakMap的键

AngularJS服务不会解析传入的Json响应到管道中的模型

无法将从服务器接收的JSON对象转换为所需的类型,因此可以分析数据

为什么Typescript只在调用方提供显式泛型类型参数时推断此函数的返回类型?

在打字脚本中对可迭代对象进行可变压缩

如何从接口中省略接口

如何在try 运行独立的文字脚本Angular 项目时修复Visual Studio中的MODULE_NOT_FOUND