Select

默认情况下,Payload 的 API 会返回给定 collection 或 global 的 所有字段。但你可能并不需要所有这些数据。有时,你可能只需要响应中的几个字段,这可以加快 Payload API 的速度,并减少从 API 发送给你的 JSON 数据量。

这就是 Payload 的 select 功能的用武之地。通过它,你可以精确指定要从 API 中检索哪些字段。

本地 API

要在本地 API中指定 select,你可以在查询中使用 select 选项:

import type { Payload } from 'payload'

// 包含模式
const getPosts = async (payload: Payload) => {
  const posts = await payload.find({
    collection: 'posts',
    select: {
      text: true,
      // 从 group 中选择特定字段
      group: {
        number: true,
      },
      // 选择 array 的所有字段
      array: true,
    }, // highlight-line
  })

  return posts
}

// 排除模式
const getPosts = async (payload: Payload) => {
  const posts = await payload.find({
    collection: 'posts',
    // 选择除 array 和 group.number 之外的所有内容
    select: {
      array: false,
      group: {
        number: false,
      },
    }, // highlight-line
  })

  return posts
}

重要提示: 为了高效执行带有 select 的查询,Payload 会在数据库级别实现你的 select 查询。因此,你的 beforeReadafterRead 钩子可能不会接收到完整的 doc。为确保某些字段始终被选中(无论 select 查询如何),以便你的钩子/访问控制可以使用,你可以使用 collection 配置中的 forceSelect 属性。

REST API

要在 REST API 中指定 select 查询,你可以在请求中使用 select 参数:

fetch(
  'https://localhost:3000/api/posts?select[color]=true&select[group][number]=true',
) // highlight-line
  .then((res) => res.json())
  .then((data) => console.log(data))

要理解这种语法,需要明白复杂的 URL 查询字符串会被解析为 JSON 对象。这个例子还不算太复杂,但更复杂的查询会不可避免地变得更难编写。

因此,我们推荐使用非常实用且广泛使用的 qs-esm 包,将你的 JSON/对象格式的查询转换为查询字符串:

import { stringify } from 'qs-esm'
import type { Where } from 'payload'

const select: Where = {
  text: true,
  group: {
    number: true,
  },
  // 这个查询可以复杂得多
  // QS 都能完美处理
}

const getPosts = async () => {
  const stringifiedQuery = stringify(
    {
      select, // 确保 `qs` 也添加 `select` 属性!
    },
    { addQueryPrefix: true },
  )

  const response = await fetch(
    `http://localhost:3000/api/posts${stringifiedQuery}`,
  )
  // 继续处理响应...
}

提醒: 使用 /api/globals 端点的 Globals 也同样适用。

defaultPopulate 集合配置属性

defaultPopulate 属性允许你指定从另一个文档填充集合时要选择的字段。这对于只需要 slug 而非整个文档的链接特别有用。

通过此功能,你可以显著减少从 RelationshipUpload 字段填充的 JSON 数据量。

例如,在你的内容模型中,可能有一个 Link 字段链接到其他页面。当你获取这些链接时,实际上只需要页面的 slug

加载所有页面内容、相关链接和其他所有内容会显得过度,并且会拖慢你的 Payload API。相反,你可以在 Pages 集合上定义 defaultPopulate 属性,这样当 Payload "填充" 相关页面时,它只会选择 slug 字段,从而返回显著减少的 JSON 数据:

import type { CollectionConfig } from 'payload'

// 可以传递 TSlug 泛型来为 `defaultPopulate` 提供类型安全。
// 如果省略,`defaultPopulate` 类型将解析为 `SelectType`。
export const Pages: CollectionConfig<'pages'> = {
  slug: 'pages',
  // 指定 `select`。
  defaultPopulate: {
    slug: true,
  },
  fields: [
    {
      name: 'slug',
      type: 'text',
      required: true,
    },
  ],
}

重要提示: 当在启用了 Uploads 的集合上使用 defaultPopulate, 并且你想选择 url 字段时,必须同时指定 filename: true, 否则 Payload 将无法构建正确的文件 URL,而是返回 url: null

populate

设置 defaultPopulate 将强制 Payload 在每次执行关联文档的 "population" 操作时,只查询并返回指定的字段。不过,你可以通过 Local API 和 REST API 中的 populate 属性来覆盖 defaultPopulate

Local API:

import type { Payload } from 'payload'

const getPosts = async (payload: Payload) => {
  const posts = await payload.find({
    collection: 'posts',
    populate: {
      // 从 "pages" collection 的关联文档中仅选择 `text` 字段
      // 无论 "pages" collection 的 `defaultPopulate` 设置为何值,
      // 都会被此处的设置覆盖,最终只返回 `text` 字段
      pages: {
        text: true,
      }, // highlight-line
    },
  })

  return posts
}

REST API:

fetch('https://localhost:3000/api/posts?populate[pages][text]=true') // highlight-line
  .then((res) => res.json())
  .then((data) => console.log(data))