请考虑以下代码:

type ResourceData = {
  user: { id: number; name: string; email: string }
  product: { id: number; name: string; price: number }
  order: { id: number; userId: number; productId: number; quantity: number }
}

type ResourceAction<T extends keyof ResourceData> = {
  type: T
  payload: ResourceData[T]
}

function fetchActionData<T extends keyof ResourceData>(
  action: ResourceAction<T>,
) {
  const actionPayload = action.payload as ResourceData[T] // <---- not working well, it's look only for id.

  switch (action.type) {
    case "product":
      const product = action.payload as ResourceData["product"]
      return `product is ${product.name}`
    case "order":
      const order = action.payload as ResourceData["order"]
      return `order is ${order.productId}`
    case "user":
      const user = action.payload as ResourceData["user"]
      return `name is ${user.name}`
    default:
      return
  }
}

const userAction: ResourceAction<"user"> = {
  type: "user",
  payload: { id: 1, name: "Alice", email: "alice@example.com" },
}

const productAction: ResourceAction<"product"> = {
  type: "product",
  payload: { id: 101, name: "Widget", price: 19.99 },
}

const orderAction: ResourceAction<"order"> = {
  type: "order",
  payload: { id: 1001, userId: 1, productId: 101, quantity: 3 },
}

console.log(fetchActionData(userAction))
console.log(fetchActionData(productAction))
console.log(fetchActionData(orderAction))

当我try 删除此代码时:

const product = action.payload as ResourceData['product'];

...并在Switch Case上创建新常量:

const actionPayload = action.payload as ResourceData[T]

使用后:

    case "user":
      return `name is ${actionPayload.name}`

TypeScrip引发以下错误:

Property 'name' does not exist on type '{ id: number; name: string; email: string; } | { id: number; name: string; price: number; } | { id: number; userId: number; productId: number; quantity: number; }'.

而当判断context时,它只包含id个字段. 为什么会发生这种情况,我如何才能修复它?

推荐答案

TypeScrip无法缩小泛型函数体中的泛型类型.因此,当您在代码分支中使用ResourceData[T],其中类型为T的函数参数已缩小为'user'时,TypeScrip不会将其视为也缩小了T类型.

这意味着ResourceData[T]仍然包括ResourceData['order'],而ResourceData['order']没有name属性,这就是为什么当您试图访问该属性时它会发出警告.

TypeScrip不能缩小泛型类型的范围,本质上是可以传递联合类型的.例如:

const unknownAction = Math.random() > 0.5 ? userAction : productAction;
fetchActionData(unknownAction); // <- fetchActionData<"user" | "product">

TypeScript Playground

当一个函数接受引用泛型类型的多个参数时,这会变得更加复杂.当该泛型类型是联合时,这些不同的参数可以使用它来引用同一函数调用中同一联合的不同成员.

TypeScript 5.3 may include a feature,允许缩小泛型函数的范围,特别是为泛型类型设置下限.但在这种情况下,这不会有什么帮助.

在泛型类型不影响函数的返回类型的特定情况下,可以使用参数类型的联合来缩小范围.需要同时缩小范围的多个参数可以通过使用带有元组类型联合的扩展语法来管理.例如:

type ResourceData = {
    user: { id: number, name: string, email: string },
    product: { id: number, name: string, price: number },
    order: { id: number, userId: number, productId: number, quantity: number },
}

// Using an immediately indexed mapped type (IIMT) to create a union
type UseResourceDataParams = {
  [Type in keyof ResourceData]: [type: Type, data: ResourceData[Type]];
}[keyof ResourceData]

function useResourceData(...[type, data]: UseResourceDataParams) {
  if (type === 'user') {
    data.name; // <- This is fine
  }
  // ...
};

TypeScript Playground

但是,我希望您的示例函数在这里始终返回相同的类型,因为您希望简化您的示例.如果它需要返回不同的类型,那么我认为您要么需要使用重载(这可能是不可靠的,并且难以管理/维护),要么像您一直在做的那样使用类型断言.

我希望您不会太反对在这种情况下使用类型断言.是的,它们可能会导致问题,但它们也是一个非常有用的工具,可以告诉打字脚本编译器一些它不知道但您知道是真的信息.正如您已经发现的,这正是您需要在这里执行的操作,以便让输入脚本理解您的功能.

Typescript相关问答推荐

对于使用另一个对象键和值类型的对象使用TS Generics

替代语法/逻辑以避免TS变量被分配之前使用." "

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

从typescript中的对象数组中获取对象的值作为类型'

访问继承接口的属性时在html中出错.角形

适当限制泛型函数中的记录

限制返回联合的TS函数的返回类型

部分更新类型的类型相等

如果数组使用Reaction-Hook-Form更改,则重新呈现表单

为什么Typescript不能推断类型?

通过泛型函数本身推断受约束的泛型参数的类型脚本问题

如何在typescript中为this关键字设置上下文

基于泛型的条件接口键

是否可以强制静态方法将同一类型的对象返回其自己的类?

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

如何键入对象转换器

如何在Reaction Native中关注屏幕时正确更新状态变量?

Typescript 是否可以区分泛型参数 void?

Typescript 从泛型推断类型

TS 排除带点符号的键