typescript 无法使用属性对泛型类型进行索引,该泛型类型应始终具有该属性

bxgwgixi  于 2022-12-05  发布在  TypeScript
关注(0)|答案(1)|浏览(209)

目标
我们的目标是创建一个fetch函数,该函数将路径和方法作为参数,返回类型应该是由名为ScoresRoutes的接口的相应response属性定义的类型。
ScoresRoutes接口中,对象的顶级键可以是任何字符串--代表一个路由。每个键都应该是一个对象,它具有一组固定的字符串--即HTTP方法。最后,每个路由和方法组合都应该是一个对象,它总是具有response属性。

行为

在下面代码的最后一行中,data的类型 * 是 * 正确的类型--即使我向ScoresRoutes接口添加了其他路由和方法--如果我尝试使用带有无效路由和方法对的customFetch,TypeScript就会抛出错误。
但是,TypeScript仍然在函数定义中显示错误,称Type '"response"' cannot be used to index type 'ScoresRoutes[R][M]'
考虑到我拥有所需的功能,我可以将错误隐藏起来,但我认为该错误表明我的操作方式错误。
编号
这是我用来描述所需接口的一般形状的更广泛的类型:

type RequestMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';
type GenericRoutes<Paths extends string> = {
  [P in Paths]: {
    [M in RequestMethod]?: {
      request?: Record<string, unknown>,
      response: Record<string, unknown> | null,
      params?: Record<string, unknown>
    }
  }
}

那么下面我来具体实现一下:

type ScoresPath = '/';
interface ScoresRoutes extends GenericRoutes<ScoresPath> {
  '/': {
    'GET': {
      response: {
        test: true
      }
    }
  }
}

最后,这是我创建的自定义fetch方法:

async function customFetch<
  R extends ScoresPath,
  M extends keyof ScoresRoutes[R]
>(
  route: R,
  method: M
): Promise<ScoresRoutes[R][M]['response']> { // This is where the TS error shows
  const data = await fetch(route, { method: method as string }).then((res) => res.json());
  return data;
}

customFetch('/', 'GET').then((data) => null); // `data` does have the proper type here
gr8qqesn

gr8qqesn1#

我相信你得到这个错误是因为response字段可能实际上不存在。这是因为从ScoresRoutes[R][M]返回的对象可能是未定义的。
解决这个问题的一种方法是使用条件类型,它允许你在访问response属性之前检查你所得到的对象的类型。

type GenericRoutes<Paths extends string> = {
    [P in Paths]: {
        [M in RequestMethod]?: RequestMethodFields
    }
}

// Extracted as a separate type
interface RequestMethodFields {
    request?: Record<string, unknown>,
    response: Record<string, unknown> | null,
    params?: Record<string, unknown>
}

// New type; we no longer get an error accessing 'response' because we have already
// checked that we have the correct type of object
type MethodResponse<
  R extends keyof ScoresRoutes,
  M extends keyof ScoresRoutes[R]
> = ScoresRoutes[R][M] extends RequestMethodFields ? ScoresRoutes[R][M]['response'] : {}

async function customFetch<
    R extends keyof ScoresRoutes,
    M extends keyof ScoresRoutes[R]
>(
    route: R,
    method: M
): Promise<MethodResponse<R, M>> { // This is where the TS error shows
    const data = await fetch(route, { method: method as string }).then((res) => res.json());
    return data;
}

Playground链接

相关问题