使用代码生成器将 TypeScript 的类型定义在前端和服务器之间共享的方法
前端使用React或Vue框架,使用TypeScript进行开发,后端使用Node.js,也使用TypeScript进行开发。
在采用这种架构时,经常会出现的问题是在API调用的逻辑中,前端和后端都重复定义了类似的数据类型。
如果在前端和服务器上直接复制并使用同一种类型,最初可能还好,但在修复错误或添加功能时很容易忘记复制,结果可能导致不同版本的差异。
即使为了类型定义而创建一个共享的存储库,每次都要更新存储库,然后在前端和服务器两者中都要升级版本,这样的工作非常繁琐。
那种时候的烦恼和困扰能够迅速解决的方便工具就是Code Generator。
只要使用一次,就会觉得为什么一开始开始使用TypeScript的时候没有做这个工具呢,开发会变得非常轻松,所以推荐使用。
Code Generator 是什么?
Code Generator是什么意思?
-
- REST APIであればswagger.json
- GraphQLであればshema.json
有一些工具可以根据输入调用服务器端的终端,自动生成函数和输入/输出类型等。有许多工具可供选择,例如
-
- REST APIであればOpenAPI Generator
GraphQLであればGraphql Code Generator
以下是一些例子。
使用OpenAPI Generator的方法
准备swagger.json文件
首先,我们需要在服务器端定义swagger.json。
如果服务器端是NestJS,只需使用Class定义请求和响应的类型,就可以自动从中生成swagger.json,非常推荐。
参考链接:https://docs.nestjs.com/openapi/cli-plugin#using-the-cli-plugin
安装OpenAPI Generator CLI
在调用API的源头(本例中是前端)上,您可以通过以下命令安装Open API Generator CLI。
$ npm install --save-dev @openapitools/openapi-generator-cli
执行命令
准备下述命令的npm脚本,然后执行npm run codegen。
"scripts": {
"codegen": "openapi-generator-cli generate -i http://<your domain>/swagger.json -g typescript-axios -o ./codegen/api-client"
}
那么,代码将自动在 ./codegen/api-client 文件夹中生成。
每个选项的意义如下说明。
顺便一提,对于NestJS,如果使用–remove-operation-id-prefix选项来删除swagger.json的operationId前缀,方法名会变得更加清晰。
4. 试着调用自动生成的代码。
假设服务器端的代码是用NestJS编写的,如下所示。
export class CreateUserBodyDto {
email: string;
}
export class CreateUserResponseDto {
name: string;
}
@Controller('users')
export class UsersController {
@Post()
createUser(@Body() createUserDto: CreateUserBodyDto): CreateUserResponseDto {
return { name: 'Test' };
}
}
当您使用此代码生成swagger.json并执行codegen命令时,您可以使用生成的代码来调用API,如下所示。
import { Configuration, DefaultApi, CreateUserBodyDto } from '../codegen/api-client';
export class ApiClientRepository extends DefaultApi {
constructor() {
super(new Configuration({ basePath: 'http://<your domain>/' }));
}
}
import { CreateUserBodyDto } from '../codegen/api-client';
const apiClientRepository = new ApiClientRepository();
const input: CreateUserBodyDto = { email: 'test@qiita.com' };
const { name } = apiClientRepository.createUser(input);
console.log(name);
在中国创建用户的方法中,参数和返回值都已定义了类型并且可以进行导入。这意味着可以在前端使用服务器端的类型定义而无需重新定义。
Graphql Code Generator的用法如下
准备一个schema.json文件
首先,在服务器端定义graphql的schema.json。如果使用NestJS作为服务器端,并采用code first的方法引入graphql,只需通过在Class中定义请求和响应的类型,就可以自动生成schema.json,因此建议使用该方法。详情请参考:https://docs.nestjs.com/graphql/quick-start#overview
安装 GraphQL Code Generator
在API的调用源(本例中为前端)上通过以下命令安装Graphql代码生成器。
$ npm install --save-dev @graphql-codegen/cli
如果要在 TypeScript 和 React(+Apollo client)中使用,请安装以下插件,并同时更新 codegen.yml。
$ npm install --save-dev @graphql-codegen/typescript @graphql-codegen/typescript-resolvers @graphql-codegen/typescript-react-apollo
overwrite: true
schema: 'http://<your domain>/graphql'
documents: 'src/**/*.graphql'
generates:
./src/graphql/types.ts:
plugins:
- 'typescript'
- 'typescript-resolvers'
- 'typescript-react-apollo'
执行命令
假设服务器端是用NestJS编写的以下示例代码。
@ArgsType()
export class SampleArgsDto {
@Field(() => String, { nullable: true })
sampleId?: string;
}
@ObjectType()
export class SampleData {
@Field()
sampleId: string;
}
@Resolver()
export class SampleResolver {
@Query(() => SampleData)
sample(@Args() { sampleId }: SampleArgsDto): SampleData {
return { sampleId };
}
}
根据此代码,在前端准备以下类型的查询。
query getSample($sampleId: String) {
sample(sampleId: $sampleId) {
sampleId
}
}
然后在npm脚本中准备以下命令,并执行npm run codegen。
"scripts": {
"codegen": "graphql-codegen --config codegen.yml"
}
然后,会在./graphql文件夹中自动生成代码。
4. 尝试调用自动生成的代码。
使用在前一步生成的代码,可以调用以下的查询。
import React, { useEffect } from 'react';
import { useGetSampleQuery } from '../graphql/types';
const Sample = () => {
const { data } = useGetSampleQuery({
variables: { sampleId: 'init' },
});
return (
<div>Response: {data?.sample.sampleId}</div>
);
};
export default Sample;
不仅仅是定义了模型结构,还创建了可以调用查询并获取数据的钩子,因此前端只需要编写查询即可,非常简单。
综上所述
REST API和GraphQL是完全不同的技术,但是通过使用代码生成器,在前端和服务器之间可以共享类型定义。
此外,还可以自动生成用于调用端点的代码,让实现变得非常容易。
虽然两者都需要一些繁琐的准备工作,但一旦引入,后续的开发将变得非常容易,所以我推荐使用。
但是请注意,一旦开始使用,就很难停止,所以在引入时请注意。