如何启用 TypeScript 以使用 Typescript 泛型和接口推断通用 HttpServiceMock 中数据对象的确切形状?

问题描述 投票:0回答:1

我目前正在使用 TypeScript,我想实现一些我真的不知道是否可行的东西(我相信它是)。

我有这个代码作为例子:

interface HttpServiceMockData<T> {
  status: number;
  data: T;
  url: string;
}

export function createHttpServiceMock<T>(data: HttpServiceMockData<T>[]) {
  return {
    get: async (url: string): Promise<{ data: T }> => {
      const res = data.find((d) => d.url === url);
      if (!res) {
        throw new Error(`No data found for url ${url}`);
      }
      return {
        data: res.data,
      };
    },
  };
}

const service = createHttpServiceMock([
  {
    url: '/users/1',
    data: {
      id: 1,
      username: 'test',
    },
    status: 200,
  },
  {
    url: 'test',
    data: {
      id: 1,
      username: 'test',
      lastname: 'test',
    },
    status: 200,
  },
]);

const res = service.get('test').then((res) => {
  res.data // I want typescript to know the exact type here ( inference power )
});

目前 TypeScript 知道要返回什么,但它不知道数据对象的确切形状,它给了我可选的姓氏,这是我不想要的。
我想传递 URL,TypeScript 应该明白我想要得到什么。

有什么想法吗?

javascript typescript typescript-generics type-inference
1个回答
1
投票

是的,这类事情是 Typescript 真正闪耀的地方,但它需要你多一点指导。

在 TS Playground 中尝试.

export function createHttpServiceMock<Services extends HttpServiceMockData<any>>(
  data: ReadonlyArray<Services>
) {
  return {
    get: async <TargetUrl extends Services['url']>(url: TargetUrl)
        : Promise<{ data: (Services & { url : TargetUrl })['data'] }> => {
      //...
    },
  };
};

简而言之,代表你的服务的discriminated union类型(

Services
)由数组组成。返回的
url
函数中的
get
参数被限制为给定服务的
url
属性。它本质上表示为 generic,因此它可以用作类型变量。对于
get
的返回类型,通过 intersecting
Services
联合和具有目标
url
属性的对象类型来选择正确的服务类型。然后选择其对应的数据属性。

此外,当您调用您的服务时,

url
属性(或服务)必须用
as const
声明,以便 Typescript 解释
url
标签literally,而不是普通的
string
s。

const service = createHttpServiceMock([
  {
    url: '/users/1' as const,
    data: {
      id: 1,
      username: 'test',
    },
    status: 200,
  }, // or
  {
    url: 'test',
    data: {
      id: 1,
      username: 'test',
      lastname: 'test',
    },
    status: 200,
  } as const,
]);

或者,如果您将服务定义为表,则可以摆脱整个联合交集业务:

type ServiceTable = { [K in string] : HttpServiceMockData<any> };
export function createHttpServiceMockTable<Services extends ServiceTable>(
  data: Services
) {
  return {
    get: async <TargetUrl extends keyof Services>(url: TargetUrl)
        : Promise<{ data: Services[TargetUrl]['data'] }> => {
      //...
    },
  };
};

const service2 = createHttpServiceMockTable({
  ['/users/1'] : {
    //...
  },
  test : {
    //...
  },
});

Typescript 手册 是获取更多背景知识的最佳资源。关于Objects的部分特别相关。

© www.soinside.com 2019 - 2024. All rights reserved.