提到 TypeScript,开发者们常常抱怨说“达到同样的效果,TypeScript 往往需要更多代码”。我理解这个说法,不过我觉得 TypeScript 泛型可以很大程度上精简代码。

TypeScript 泛型是什么?

TS 泛型可以将 TS 接口抽象化。使用泛型,可以将接口作为参数传递给另一个接口。

下面的代码展示了 API 响应的正确和错误示例。

// successful response ✅
{
	status: 'ok',
	responseCode: 200,
	data: {...}
}

// error response ❌
{
	responseCode: 500,
	errorMessage: "Something went wrong 😅";
}

你可以使用泛型创建如下示例,而不用为每个响应编写接口。

interface ApiResponse<T>{
	errorMessage?: string;
	responseCode?: string;
	data?: T;
}
interface UserData {
	name: string;
	email: string;
}
const response: ApiResponse<UserData> = {}

连接泛型和函数

假设我们有一个函数用于向后端发出 API 请求。

const getRequestTo = (endpoint: string) => {
	return fetch(process.env.BE_HOST + endpoint).then(res => res.json())
}

const userResponse = getRequestTo('/user-data')

userResponse 的类型是 any。这里我们可以更好地使用 TypeScript。

const getRequestTo = async <R>(endpoint: string): Promise<ApiResponse<R>> => {
	const request = await fetch(process.env.BE_HOST + endpoint);
	const response = await request.json();
	return response;
};

我们为 POST 请求创建一个相似的函数,输入 JSON 参数,类型为 B,服务器返回一个 JSON 响应,类型为 R

const postRequestTo = async <B, R>(
	endpoint: string,
	body: B
): Promise<ApiResponse<R>> => {
	const request = await fetch(process.env.BE_HOST + endpoint, {
		method: "POST",
		body: JSON.stringify(body),
	});

	const response = await request.json();

	return response;
};

类似的,也可以有一个 PATCH 请求函数,处理一个实体的局部更新。

const patchRequestTo = async <B, R>(endpoint: string,body: Partial<B>): Promise<ApiResponse> => {
	const request = await fetch(process.env.BE_HOST + endpoint, {
    	method: "PATCH",
	    body: JSON.stringify(body)
    });
	const response = await request.json();
	return response;
};

如下示例:

interface RequestBody {}
interface Response {}

const createPost = await postRequestTo<RequestBody, Response>('/post', postData);

const updatePost = await patchRequestTo<RequestBody, Response>('/post', {
	title: "new name"
});

用一个简单的 JavaScript 类将所有内容结合起来:

class ApiService<T> {
	constructor(entitySlug: string) {
		this.entitySlug = entitySlug;
	}

	private entitySlug: string;

	getOne = async (id: string): Promise<APIResponse<T>> => {
		const request = await fetch(process.env.BE_HOST + this.entitySlug + '/' + id);
		const response = await request.json();
		return response;
	};
    
	getList = async (): Promise<APIResponse<T[]>> => {
		const request = await fetch(process.env.BE_HOST + this.entitySlug);
		const response = await request.json();
		return response;
	};

	create = async (body: Omit<T, 'id' | 'created' | 'updated'>): Promise<APIResponse<T>> => {
		const request = await fetch(process.env.BE_HOST + this.entitySlug, {
			method: 'POST',
			body: JSON.stringify(body)
		});

		const response = await request.json();
		return response;
	};
    
	update = async (
		body: Omit<Partial<T>, 'id' | 'created' | 'updated'>
	): Promise<APIResponse<T>> => {
		const request = await fetch(process.env.BE_HOST + this.entitySlug + '/' + body.id, {
			method: 'PATCH',
			body: JSON.stringify(body)
		});
		const response = await request.json();
		return response;
	};
    
	delete = async (id: string): Promise<void> => {
		const request = await fetch(process.env.BE_HOST + this.entitySlug + '/' + id, {
			method: 'DELETE'
		});
		const response = await request.json();
		return response;
	};
};

然后创建如下实体服务:

export const postService = new ApiService<Post>('/post');

结果如下:

image-70
VS Code 根据我们的 TypeScript 配置提供自动建议

如果适当配置,TypeScript 有可能将开发人员的体验提升十倍。这些是使配置过程更简易的一些方式,希望这可以帮助你在代码库中更好地使用 TypeScript。

原文:How TypeScript Generics Help You Write Less Code,作者:Utkarsh Bhimte