与GraphQL配合
GraphQL与TypeScript的天然契合
GraphQL的强类型特性与TypeScript简直是天作之合。GraphQL schema本身就是一个类型系统,而TypeScript也是类型系统,两者结合能产生强大的开发体验。当使用TypeScript编写GraphQL客户端或服务端代码时,类型安全能贯穿整个应用开发流程。
// 一个典型的GraphQL查询与TypeScript类型结合示例
type User = {
id: string;
name: string;
email: string;
posts: Post[];
};
type Post = {
id: string;
title: string;
content: string;
};
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
posts {
id
title
}
}
}
`;
类型生成工具链
现代GraphQL开发中,类型生成工具是必不可少的。常用的工具如graphql-code-generator
可以自动从GraphQL schema生成TypeScript类型定义。
# 安装graphql-code-generator
npm install -D @graphql-codegen/cli
npm install -D @graphql-codegen/typescript @graphql-codegen/typescript-operations
配置codegen.yml
文件:
schema: "http://localhost:4000/graphql"
documents: "./src/**/*.graphql"
generates:
./src/generated/graphql.ts:
plugins:
- "typescript"
- "typescript-operations"
服务端类型安全
在GraphQL服务端开发中,TypeScript可以帮助构建类型安全的resolver。使用graphql
和type-graphql
等库可以完美集成。
import { Field, ID, ObjectType, Query, Resolver } from 'type-graphql';
@ObjectType()
class User {
@Field(() => ID)
id: string;
@Field()
name: string;
@Field()
email: string;
}
@Resolver()
class UserResolver {
@Query(() => [User])
async users(): Promise<User[]> {
// 实际数据库查询逻辑
return db.users.findMany();
}
}
客户端查询类型化
Apollo Client与TypeScript结合使用时,可以自动推断查询结果的类型。这大大减少了手动定义类型的需要。
import { useQuery } from '@apollo/client';
import { GetUserQuery, GetUserQueryVariables } from './generated/graphql';
function UserProfile({ userId }: { userId: string }) {
const { data, loading, error } = useQuery<GetUserQuery, GetUserQueryVariables>(
GET_USER,
{
variables: { id: userId },
}
);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h1>{data?.user.name}</h1>
<p>{data?.user.email}</p>
</div>
);
}
输入类型验证
GraphQL的输入类型与TypeScript结合,可以在编译时捕获许多潜在的错误。
import { InputType, Field } from 'type-graphql';
@InputType()
class CreateUserInput {
@Field()
name: string;
@Field()
email: string;
@Field()
password: string;
}
@Resolver()
class UserResolver {
@Mutation(() => User)
async createUser(@Arg('input') input: CreateUserInput): Promise<User> {
// 输入已经通过类型检查
return userService.create(input);
}
}
高级类型技巧
利用TypeScript的高级类型特性,可以创建更灵活的GraphQL类型工具。
type PickGraphQLFields<T, K extends keyof T> = {
[P in K]: T[P];
};
// 使用示例
type PartialUser = PickGraphQLFields<User, 'id' | 'name'>;
// 对应GraphQL片段
const USER_FRAGMENT = gql`
fragment UserInfo on User {
id
name
}
`;
错误处理模式
结合TypeScript的联合类型,可以更好地处理GraphQL错误响应。
type GraphQLError = {
message: string;
locations?: { line: number; column: number }[];
path?: string[];
};
type GraphQLResponse<T> = {
data?: T;
errors?: GraphQLError[];
};
async function fetchGraphQL<T, V = Record<string, unknown>>(
query: string,
variables?: V
): Promise<GraphQLResponse<T>> {
const response = await fetch('/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables }),
});
return response.json();
}
性能优化
TypeScript可以帮助识别不必要的GraphQL字段请求,优化网络性能。
type RequiredFields<T> = {
[K in keyof T]-?: undefined extends T[K] ? never : K;
}[keyof T];
function ensureFields<T>(obj: T, fields: RequiredFields<T>[]): void {
fields.forEach((field) => {
if (obj[field] === undefined) {
throw new Error(`Missing required field: ${String(field)}`);
}
});
}
// 使用示例
const user = await fetchUser();
ensureFields(user, ['id', 'name']); // 确保这些字段被请求
自定义Scalar类型
GraphQL的自定义Scalar类型可以完美映射到TypeScript类型。
import { GraphQLScalarType, Kind } from 'graphql';
import { Scalar } from 'type-graphql';
@Scalar('Date')
class DateScalar {
description = 'Date custom scalar type';
parseValue(value: string): Date {
return new Date(value); // 从客户端输入
}
serialize(value: Date): string {
return value.toISOString(); // 发送到客户端
}
parseLiteral(ast: any): Date | null {
if (ast.kind === Kind.STRING) {
return new Date(ast.value);
}
return null;
}
}
类型安全的订阅
GraphQL订阅也能享受TypeScript的类型安全优势。
import { PubSub } from 'graphql-subscriptions';
import { Subscription, Root, Args } from 'type-graphql';
const pubSub = new PubSub();
@Resolver()
class MessageResolver {
@Subscription(() => Message, {
topics: 'MESSAGE_ADDED',
filter: ({ payload, args }) => payload.channelId === args.channelId,
})
messageAdded(
@Root() message: Message,
@Args() { channelId }: { channelId: string }
): Message {
return message;
}
}
工具库集成
流行的GraphQL工具库如urql
和react-query
也提供了优秀的TypeScript支持。
import { useQuery } from 'urql';
import { graphql } from './gql';
const GetUserDocument = graphql(`
query GetUser($id: ID!) {
user(id: $id) {
id
name
}
}
`);
function UserComponent({ id }: { id: string }) {
const [result] = useQuery({
query: GetUserDocument,
variables: { id },
});
// result.data.user 已经具有正确的类型
}
测试中的类型安全
在测试GraphQL API时,TypeScript也能提供帮助。
import { createTestClient } from 'apollo-server-testing';
import { server } from './server';
import { gql } from 'apollo-server';
const { query } = createTestClient(server);
test('fetch user', async () => {
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
}
}
`;
const result = await query({
query: GET_USER,
variables: { id: '1' },
});
// TypeScript知道result.data的可能结构
expect(result.data?.user?.name).toBeDefined();
});
前后端类型共享
通过代码生成,可以实现前后端类型定义的共享。
// 共享类型定义
export type User = {
id: string;
name: string;
email: string;
};
// 后端resolver
@Resolver()
class UserResolver {
@Query(() => [User])
async users(): Promise<User[]> {
// ...
}
}
// 前端组件
function UserList({ users }: { users: User[] }) {
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
复杂联合类型处理
GraphQL的联合类型和接口可以很好地映射到TypeScript的联合类型。
type SearchResult = Book | Author;
interface Book {
__typename: 'Book';
title: string;
author: string;
}
interface Author {
__typename: 'Author';
name: string;
books: string[];
}
function renderSearchResult(result: SearchResult) {
switch (result.__typename) {
case 'Book':
return <BookComponent book={result} />;
case 'Author':
return <AuthorComponent author={result} />;
default:
// TypeScript会确保所有情况都被处理
const _exhaustiveCheck: never = result;
return _exhaustiveCheck;
}
}
类型安全的GraphQL指令
TypeScript可以帮助验证GraphQL指令的使用。
import { Directive, DirectiveLocation, GraphQLDirective } from 'graphql';
const deprecatedDirective = new GraphQLDirective({
name: 'deprecated',
locations: [
DirectiveLocation.FIELD_DEFINITION,
DirectiveLocation.ENUM_VALUE,
],
args: {
reason: {
type: GraphQLString,
defaultValue: 'No longer supported',
},
},
});
// TypeScript类型可以确保指令参数正确
type DirectiveArgs = {
reason?: string;
};
function applyDirective(field: string, args: DirectiveArgs) {
// 实现指令逻辑
}
性能监控与类型
结合TypeScript可以创建类型化的性能监控工具。
type GraphQLMetrics = {
operationName: string;
executionTime: number;
fieldResolveTimes: Record<string, number>;
errors?: GraphQLError[];
};
function trackPerformance<T>(
operation: string,
fn: () => Promise<T>
): Promise<{ result: T; metrics: GraphQLMetrics }> {
const start = performance.now();
const fieldTimes: Record<string, number> = {};
return fn().then((result) => ({
result,
metrics: {
operationName: operation,
executionTime: performance.now() - start,
fieldResolveTimes: fieldTimes,
},
}));
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:与Express/Koa框架
下一篇:与Web Workers