TypeScript + React最佳实践 - 第三节:Service类型化(2) - Graphql类型化

1,173 阅读3分钟

前言

TypeScript + React 类型安全三件套:Component、Redux、和Service类型化。

Service-Graphql 类型化

Graphql 是 Facebook 推出的接口查询语言——比较成熟且主流的前后端交互技术方案之一。

Graphql 的定位

大概是在19年9月的时候,我决定在技术探索性项目里使用 Graphql——但如何把 Graphql 整合到“类型安全三件套”的技术体系里,却一直没有一个比较满意的技术方案。

在 React 里边,Graphql 比较经典的使用方式,有将 Graphql Schema 和 Query 转换成高阶组件或者 Hooks,但这其实破坏了我当时正大力推行的单向数据流【Redux + useReducer】基本技术方案。

最终,经过反复思考,我觉得把 Graphql 定位为 Restful API 的替代方案之一,把它当做纯粹的 Service 层来认知和处理,这样 Graphql 和既有基本技术方案的冲突是最小,从而能无缝的整合进来。

Graphql 的类型化

Graphql 语言本身

作为 TypeScript 的深度使用者,肯定也希望编写 Graphql 的时候,也能实现静态类型检测——结构、类型是否正确,这些低级错误,我们希望有一个途径能够实时的检测并抛出来。

很幸运的,类似于 TypeScript,有一个 Graphql Language Server,可以通过 Schema 检测我们编写的 Query 语句是否有错误。

已 VSCode 编辑器为例,我们可以:

  • 安装 Prisma.vscode-graphql 插件
  • 安装 watchman
  • 在项目根目录里配置 .graphqlconfig,比如配置:
{
  "schemaPath": "schema/schema.graphql",
  // @IMP: 踩过坑,一定要 "**/*",不然不生效
  "includes": ["**/*.graphql", "**/*.gql"],
  "excludes": ["node_modules"],
  "extensions": {
    "endpoints": {
      "dev": "http://127.0.0.1/graphql",
      "prod": "http://www.naotu.com/graphql"
    }
  }
}

这样就可以实现对 Query 语句的实时静态检测。

Tips:这个插件现在有个坑就是不能动态监测schema的变化——shema变化,暂时可能需要重启vscode才行。

gen Service

同样,我们需要一个工具将接口文档 Schema 和编写的 Query 语句转成 TypeScript Service 调用代码,这也有开源的方案和工具 @graphql-codegen。

安装依赖:

npm i -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-graphql-request

配置 codegen.yaml:

schema: ./schema/schema.graphql
documents: ./src/**/*.graphql
generates:
  src/models/service.ts:
    plugins:
      # 引入本插件以生成符合规范的 Service 代码
      - 'node_modules/@bit/ks-ef-common.graphql-codegen-tkit-plugin'
    config:
      operationResultSuffix: Res
  src/models/types.d.ts:
    plugins:
      - typescript
      - typescript-operations
    config:
      globalNamespace: true
      flattenGeneratedTypes: true
      exportFragmentSpreadSubTypes: true
      # immutableTypes: true
      # avoidOptionals: true
      operationResultSuffix: Res

因为默认的 @graphql-codegen/typescript-graphql-request 提供的 Service 模板并不是基础技术方案所需要的,所以通过 graphql-codegen-tkit-plugin 插件对 typescript-graphql-request 进行修改:

const {
  GraphQLRequestVisitor,
  validate,
  plugin
} = require('@graphql-codegen/typescript-graphql-request');

const TypeTpl = `import { NonStandardAjaxPromise, ExtraFetchParams } from '@tkit/ajax';`;

const newPlugin = function() {
  // eslint-disable-next-line prefer-rest-params
  const args = [].slice.call(arguments, 0);
  // eslint-disable-next-line prefer-spread
  const { prepend, content } = plugin.apply(null, args);
  return {
    prepend: prepend.map(i => i.replace(/graphql-request/g, '@tkit/ajax/lib/graphql')),
    content:
      TypeTpl +
      content
        .replace(/\): Promise</g, ', opt?: ExtraFetchParams): NonStandardAjaxPromise<')
        .replace(/variables\)/g, 'variables, opt)')
        .replace(/graphql-request/g, '@tkit/ajax/lib/graphql')
        .replace(/\( ,/, '(')
  };
};

module.exports = {
  GraphQLRequestVisitor,
  plugin: newPlugin,
  validate
};

"gql": "graphql-codegen" 添加到 package.json,执行:

npm run gql

即可生成类型化的 Service 代码

service.ts 示例:

import { GraphQLClient } from '@tkit/ajax/lib/graphql';
import { print } from 'graphql';
import gql from 'graphql-tag';
import { NonStandardAjaxPromise, ExtraFetchParams } from '@tkit/ajax';
export const DocumentFragmentDoc = gql`
  fragment Document on Doc {
    id
    name
    owner
    type
    is_shared
    parent_id
    modify_time
    snapshot_id
  }
`;
export const DocListDocument = gql`
  query docList($id: String!, $withDocId: Boolean = false) {
    doc(id: $id) @include(if: $withDocId) {
      ...Document
    }
    docList(id: $id) {
      ...Document
    }
  }
  ${DocumentFragmentDoc}
`;

...

types.d.ts 示例:

export type Maybe<T> = T | null;
/** All built-in and custom scalars, mapped to their actual values */
export interface Scalars {
  ID: string;
  String: string;
  Boolean: boolean;
  Int: number;
  Float: number;
}

export interface DocDocSnapshotArgs {
  id: Scalars['String'];
}

...

参考资料