
在前端开发和测试中,我们经常需要模拟http服务来解耦前后端开发或编写单元测试。一个常见的模式是创建一个通用的httpservicemock,它接受一个包含模拟数据的数组,并根据请求的url返回相应的数据。然而,在使用typescript时,我们可能会遇到一个挑战:尽管typescript能够识别返回值的整体类型(例如promise<{ data: t }>),但当t是一个泛型类型且模拟数据数组中包含多种不同形状的数据时,typescript往往无法精确推断出特定url对应数据的具体结构,导致属性被标记为可选或类型过于宽泛。
考虑以下初始实现:
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,
},
]);
service.get('test').then((res) => {
// 此时,res.data 的类型是 { id: number; username: string; lastname?: string; }
// TypeScript 将 lastname 推断为可选属性,因为并非所有模拟数据都包含它。
// 我们希望当 url 为 'test' 时,res.data 能够精确推断出 { id: number; username: string; lastname: string; }
console.log(res.data.lastname); // 可能提示 lastname 是可选的
});在这个例子中,createHttpServiceMock 函数的泛型参数T被推断为所有data对象中data属性的联合类型,这导致了lastname属性被标记为可选。为了解决这个问题,我们需要更精确地指导TypeScript,使其能够根据传入的URL字面量来推断出对应的具体数据类型。
要实现精确的类型推断,我们需要利用TypeScript的以下高级特性:
下面是改进后的createHttpServiceMock函数实现:
interface HttpServiceMockData<T, U extends string> {
status: number;
data: T;
url: U; // 将 url 类型参数化
}
export function createHttpServiceMock<Services extends HttpServiceMockData<any, string>>(
data: ReadonlyArray<Services>
) {
return {
get: async <TargetUrl extends Services['url']>(url: TargetUrl)
: Promise<{ data: (Services & { url : TargetUrl })['data'] }> => {
// 运行时实现保持不变,类型推断在编译时完成
const res = (data as Services[]).find((d) => d.url === url);
if (!res) {
throw new Error(`No data found for url ${url}`);
}
return {
data: res.data as (Services & { url : TargetUrl })['data'], // 进行类型断言以匹配返回类型
};
},
};
}代码解析:
使用 as const 断言:
为了让TypeScript将url属性推断为字面量类型,而不是宽泛的string,我们需要在定义模拟数据时使用as const断言。
const service = createHttpServiceMock([
{
url: '/users/1' as const, // 明确将 url 声明为字面量类型
data: {
id: 1,
username: 'test',
},
status: 200,
},
{
url: 'test' as const, // 或者直接将整个对象声明为 as const
data: {
id: 1,
username: 'test',
lastname: 'test',
},
status: 200,
},
]);
service.get('test').then((res) => {
// 此时,res.data 的类型将精确推断为 { id: number; username: string; lastname: string; }
console.log(res.data.lastname); // 不再提示可选,类型安全
});
service.get('/users/1').then((res) => {
// 此时,res.data 的类型将精确推断为 { id: number; username: string; }
// console.log(res.data.lastname); // 报错:Property 'lastname' does not exist on type '{ id: number; username: string; }'
});通过这种方式,我们成功地利用了TypeScript的强大类型系统,实现了根据URL精确推断返回数据形状的目标。
如果你的模拟服务配置更适合用一个对象而不是数组来表示,那么可以采用基于对象表的方案。这种方式可以简化类型推断,因为它天然地将URL作为键,将服务配置作为值,使得类型查找更加直观。
type ServiceTable = { [K: string]: HttpServiceMockData<any, string> };
export function createHttpServiceMockTable<Services extends ServiceTable>(
data: Services
) {
return {
get: async <TargetUrl extends keyof Services>(url: TargetUrl)
: Promise<{ data: Services[TargetUrl]['data'] }> => {
// 运行时实现
const res = data[url];
if (!res) {
throw new Error(`No data found for url ${url}`);
}
return {
data: res.data as Services[TargetUrl]['data'], // 类型断言
};
},
};
}
// 使用示例
const service2 = createHttpServiceMockTable({
'/users/1': {
url: '/users/1',
data: {
id: 1,
username: 'test',
},
status: 200,
},
'test': {
url: 'test',
data: {
id: 1,
username: 'test',
lastname: 'test',
},
status: 200,
},
} as const); // 同样需要 as const 来确保键是字面量类型
service2.get('test').then((res) => {
// 此时,res.data 的类型将精确推断为 { id: number; username: string; lastname: string; }
console.log(res.data.lastname);
});
service2.get('/users/1').then((res) => {
// 此时,res.data 的类型将精确推断为 { id: number; username: string; }
// console.log(res.data.lastname); // 报错
});代码解析:
这种基于对象表的方案在类型推断上更为直观和简洁,因为它直接利用了JavaScript对象的键值对结构。同样,为了让keyof Services能够精确地推断出字面量键,传入的配置对象也需要使用as const断言。
通过本教程,我们深入探讨了如何利用TypeScript的泛型、字面量类型、可辨识联合类型和索引访问类型,解决了通用HTTP服务模拟中数据类型推断不精确的问题。掌握这些高级特性将极大地提升你在复杂应用中构建健壮、类型安全代码的能力。
以上就是利用TypeScript泛型与接口实现HTTP服务模拟数据精确类型推断教程的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号