生成 TypeScript 接口

在 Payload 中构建自定义功能时,比如 插件钩子访问控制函数、自定义视图GraphQL 查询/变更或其他任何功能,你可能会需要从 Payload 配置动态生成自己的 TypeScript 类型。

类型生成脚本

在 Payload 项目中运行以下命令,根据你的 Payload 配置生成类型:

payload generate:types

当你需要重新生成类型时,可以随时运行此命令,然后直接在 Payload 代码中使用这些类型。

禁用 declare 声明

默认情况下,generate:types 会在你的类型文件中添加一个 declare 声明,这会在 Payload 中自动启用类型推断。

但是,如果你在其他仓库中使用 payload-types.ts 文件,最好禁用这个 declare 声明,这样在使用 Payload 类型但没有安装 Payload 的项目中就不会出现 TS 错误。

// payload.config.ts
{
  // ...
  typescript: {
    declare: false, // 如果不设置,默认为 true
  },
}

如果禁用了 declare 模式,你需要手动在代码中添加 declare 声明,以便 Payload 类型能够被识别。以下示例展示了如何在 payload.config.ts 文件中声明你的类型:

import { Config } from './payload-types'

declare module 'payload' {
  export interface GeneratedTypes extends Config {}
}

自定义输出文件路径

你可以通过在 Payload 配置中添加属性来指定类型生成的位置:

// payload.config.ts
{
  // ...
	typescript: {
    // 默认值为: path.resolve(__dirname, './payload-types.ts')
		outputFile: path.resolve(__dirname, './generated-types.ts'),
	},
}

上述示例将你的类型文件 generated-types.ts 生成在 Payload 配置文件同级目录下。

自定义生成类型

Payload 基于 JSON schema 生成你的类型。你可以通过向 typescript.schema 传递函数来扩展该 JSON schema,从而扩展生成的类型:

// payload.config.ts
{
  // ...
  typescript: {
    schema: [
      ({ jsonSchema }) => {
        // 在此处修改 JSON schema
        jsonSchema.definitions.Test = {
          type: 'object',
          properties: {
            title: { type: 'string' },
            content: { type: 'string' },
          },
          required: ['title', 'content'],
        }
        return jsonSchema
      },
    ]
  }
}

// 这将在你的 payload-types.ts 中生成以下类型:

export interface Test {
  title: string
  content: string
}

该函数接收现有的 JSON schema 作为参数,并返回修改后的 JSON schema。对于希望生成自己类型的插件来说,这个功能会很有用。

使用示例

例如,我们来看以下这个简单的 Payload 配置:

import type { Config } from 'payload'

const config: Config = {
  serverURL: process.env.NEXT_PUBLIC_SERVER_URL,
  admin: {
    user: 'users',
  },
  collections: [
    {
      slug: 'users',
      fields: [
        {
          name: 'name',
          type: 'text',
          required: true,
        },
      ],
    },
    {
      slug: 'posts',
      admin: {
        useAsTitle: 'title',
      },
      fields: [
        {
          name: 'title',
          type: 'text',
        },
        {
          name: 'author',
          type: 'relationship',
          relationTo: 'users',
        },
      ],
    },
  ],
}

通过生成类型,我们将得到一个包含以下两个 TypeScript 接口的文件:

export interface User {
  id: string
  name: string
  email?: string
  resetPasswordToken?: string
  resetPasswordExpiration?: string
  loginAttempts?: number
  lockUntil?: string
}

export interface Post {
  id: string
  title?: string
  author?: string | User
}

自定义字段接口

对于 arrayblockgroup 和命名 tab 字段,你可以生成顶层可复用的接口。以下是一个 group 字段配置示例:

{
  type: 'group',
  name: 'meta',
  interfaceName: 'SharedMeta', <-- 这里!!
  fields: [
    {
      name: 'title',
      type: 'text',
    },
    {
      name: 'description',
      type: 'text',
    },
  ],
}

这将生成:

// 一个顶层可复用的接口!!
export interface SharedMeta {
  title?: string
  description?: string
}

// 在集合接口中的使用示例
export interface Collection1 {
  // ...其他字段
  meta?: SharedMeta
}

命名冲突警告

由于这些类型会被提升到顶层,你需要注意可能发生的命名冲突。例如,如果你有一个名为 Meta 的集合,同时又创建了一个名为 Meta 的接口,它们就会发生冲突。建议通过在接口名称后追加字段类型来限定作用域,例如使用 MetaGroup 或类似的命名方式。

使用你的类型

现在你的类型已经生成,Payload 的 Local API 将会具备类型提示。通常用户会希望在前端代码中使用这些类型,我们推荐使用 Payload 生成类型文件,然后将其复制到前端代码库中。这是将你的类型引入前端代码库的最简单方法。

添加 npm 脚本

重要提示

Payload 需要能够找到你的配置才能生成类型定义。

Payload 会自动尝试定位你的配置文件,但有时可能无法找到。例如,如果你在 /src 目录或类似结构中工作,你需要通过环境变量手动告诉 Payload 在哪里查找你的配置。如果这种情况适用于你,可以创建一个 npm 脚本来简化类型生成过程。

要添加一个 npm 脚本来生成类型并告诉 Payload 在哪里查找配置,请打开你的 package.json 并将 scripts 属性更新为以下内容:

{
  "scripts": {
    "generate:types": "PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types",
  },
}

现在你可以运行 pnpm generate:types 来轻松生成类型定义。