使用计算属性时如何设置类型?

How to set type when computed property is used?

我有两个接口。

interface Person {
    name: string;
    weight: number;
}

interface Dog {
    name: string;
    mass: number
}

const attribute = isDog ? 'mass' : 'weight';
const player: Person | Dog = "Object with specific type"

if (player[attribute] === 0) return;

我有这个错误:
TS7053:元素隐式具有 'any' 类型,因为“质量”类型的表达式 | "weight"' 不能用于索引类型 'Person | Dog'.

API 可以 return 狗或人,这取决于其他参数。我想知道告诉 TS 如果玩家是人使用 'weight' 并且如果玩家是狗使用 'mass' 的最佳方式是什么。我不想使用“任何”类型。

TypeScript 实际上在这里很有用:例如,如果 attribute"mass",但 playerPerson,那么它将是一个合理的错误,因为mass 属性 在 Person 个实例上不存在。

你要避免这个错误,你需要改变方法。您可以添加 player 中实际存在的 attribute 的运行时检查,但我认为您应该使用 isDog 变量(它更符合逻辑):

const isDog = "mass" in player;
const mass = isDog ? player.mass : player.weight;

if (mass === 0) return;

Try it.

用户@jonsharpe 在 中提出了一个有价值的模式:将相似的功能集中在一个 属性 上,即,如果 weightmass 传达相同的想法,他们应该被称为相同的(注意 PersonDog 如何有 name 属性,这传达了相同的想法)。通常这是通过实现通用接口的对象完成的(例如本例中的 Massive)。

你可以这样做:

interface Massive {
  mass: number;
}

interface Person extends Massive {
  name: string;
  // other Person-specific properties, no need to repeat `mass`
}

interface Dog extends Massive {
  name: string;
  // other Dog-specific properties, no need to repeat `mass`
}

function doSomethingIfHasMass(value: Massive) {
  if (value.mass === 0) return;

  // otherwise do the thing
}

const player: Person | Dog;

doSomethingIfHasMass(player);
// shouldn't cause compile errors,
// because `Person | Dog` is assignable to `Massive`,
// since each `Person` and `Dog` individually is `Massive`

Try it.

这应该足以解决您的问题(尽管我建议也为 name: string 属性 创建一个 Named 接口)。然而,还有另一个功能,您会发现它很有用。虽然 hasMass 回答了问题 “物体有质量吗?”(如果它的质量大于零,它就有),isMassive 会回答问题 “该对象是否符合 Massive 接口?”(如果它具有“质量”属性,则符合):

function isMassive(value: object): value is Massive {
  return "mass" in value;
}

现在你可以在你的函数中提供各种对象,如果对象的形状不正确,它会默默地失败而不会出错(这种行为是否适合你是你的决定):

function doSomethingIfMassiveAndHasMass(value: object) {
  if (!isMassive(value)) return;

  if (value.mass === 0) return;

  // otherwise do the thing
}

declare const something: object;

doSomethingIfMassiveAndHasMass(something);
// shouldn't cause compile errors,
// because while any object could be passed in this function,
// only `Massive` objects with non-zero mass will actually trigger its logic

Try it, also see an example.