阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 与GraphQL配合

与GraphQL配合

作者:陈川 阅读数:57117人阅读 分类: TypeScript

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。使用graphqltype-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工具库如urqlreact-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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌