替换为自定义 React 组件
Payload 的 Admin Panel 设计尽可能简洁直观,以便轻松定制并完全控制 UI。为了实现这种级别的定制化,Payload 提供了一种模式,让你可以通过 Payload Config 提供自己的 React 组件。
Payload 中的所有 Custom Components 默认都是 React Server Components。这使得可以直接在前端使用 Local API。几乎 Admin Panel 的每个部分都可以使用 Custom Components,实现极致的细粒度控制。
Payload 中有四种主要的 Custom Components 类型:
要替换为你自己的 Custom Component,首先确定与你目标相符的作用域,查阅可用组件列表,然后构建你的 React 组件。
定义自定义组件
当 Payload 编译 Admin Panel 时,它会检查你的配置中是否有自定义组件。当检测到时,Payload 会用自己的默认组件替换为你的组件,或者如果默认没有组件,则直接渲染你的组件。虽然 Payload 中有许多地方支持自定义组件,但每个组件都使用组件路径以相同的方式定义。
要添加自定义组件,请在 Payload 配置中指向其文件路径:
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
logout: {
Button: '/src/components/Logout#MyComponent', // highlight-line
},
},
},
})
注意: 所有自定义组件都可以是 Server Components 或 Client Components,具体取决于文件顶部是否存在 'use client'
指令。
组件路径
为了确保 Payload 配置完全兼容 Node.js 并尽可能轻量,组件不会直接导入到你的配置中。相反,它们通过文件路径标识,由 Admin Panel 自行解析。
默认情况下,组件路径是相对于项目根目录的。根目录可以是当前工作目录,或者在 config.admin.importMap.baseDir
中指定的目录。
使用命名导出的组件可以通过在路径后追加 #
加上导出名称来标识,或者使用 exportName
属性。如果组件是默认导出,则可以省略这部分。
import { buildConfig } from 'payload'
import { fileURLToPath } from 'node:url'
import path from 'path'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const config = buildConfig({
// ...
admin: {
importMap: {
baseDir: path.resolve(dirname, 'src'), // highlight-line
},
components: {
logout: {
Button: '/components/Logout#MyComponent', // highlight-line
},
},
},
})
在这个例子中,我们将基础目录设置为 src
目录,并在组件路径字符串中省略了 /src/
部分。
组件配置
虽然自定义组件通常被定义为字符串,但你也可以传入一个包含额外选项的对象:
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
logout: {
// highlight-start
Button: {
path: '/src/components/Logout',
exportName: 'MyComponent',
},
// highlight-end
},
},
},
})
以下是可用的配置选项:
Property | 描述 |
---|---|
clientProps | 如果组件是客户端组件,传递给自定义组件的属性。更多详情。 |
exportName | 除了在组件路径中使用 # 声明命名导出外,你也可以省略路径中的命名导出,在这里传入。 |
path | 自定义组件的文件路径。命名导出可以通过 # 分隔符附加在路径末尾。 |
serverProps | 如果组件是服务端组件,传递给自定义组件的属性。更多详情。 |
关于如何构建自定义组件的详细信息,请参阅构建自定义组件。
导入映射表
为了让 Payload 能够使用组件路径,系统会自动在 src/app/(payload)/admin/importMap.js
或 app/(payload)/admin/importMap.js
位置生成一个"导入映射表"。该文件包含了你配置中的所有自定义组件,并按各自的路径进行索引。当 Payload 需要查找组件时,会使用此文件来找到正确的导入路径。
导入映射表会在以下情况下自动重新生成:
- 启动时
- 热模块替换(HMR)运行时
你也可以运行
payload generate:importmap
命令手动重新生成。
覆盖导入映射表位置
通过 config.admin.importMap.importMapFile
属性,你可以覆盖导入映射表的位置。这在以下情况下很有用:
- 想要将导入映射表放在不同位置时
- 想要使用自定义文件名时
import { buildConfig } from 'payload'
import { fileURLToPath } from 'node:url'
import path from 'path'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const config = buildConfig({
// ...
admin: {
importMap: {
baseDir: path.resolve(dirname, 'src'),
importMapFile: path.resolve(
dirname,
'app',
'(payload)',
'custom-import-map.js',
), // highlight-line
},
},
})
自定义导入
如有需要,可以将自定义项添加到 Import Map 中。这主要适用于插件开发者,他们需要添加未被引用在已知位置的自定义导入。
要在 Import Map 中添加自定义导入,请使用 Payload Config 中的 admin.dependencies
属性:
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// ...
dependencies: {
myTestComponent: {
// myTestComponent 是键名 - 可以是任意名称
path: '/components/TestComponent.js#TestComponent',
type: 'component',
clientProps: {
test: 'hello',
},
},
},
},
})
构建自定义组件
Payload 中的所有自定义组件默认都是 React Server Components。这使得可以直接在前端使用 Local API 等功能。
默认属性
为了尽可能简化自定义组件的构建过程,Payload 会自动提供一些常用属性,例如 payload
类和 i18n
对象。这意味着在 Admin Panel 中构建自定义组件时,你无需自行获取这些属性。
以下是一个示例:
import React from 'react'
import type { Payload } from 'payload'
async function MyServerComponent({
payload, // highlight-line
}: {
payload: Payload
}) {
const page = await payload.findByID({
collection: 'pages',
id: '123',
})
return <p>{page.title}</p>
}
每个自定义组件默认会接收以下属性:
属性 | 描述 |
---|---|
payload | Payload 类。 |
i18n | i18n 对象。 |
提醒: 所有自定义组件还会接收与所渲染组件相关的其他各种属性。完整属性列表请参考 Root Components、Collection Components、Global Components 或 Field Components。
自定义属性
你也可以向自定义组件传递自定义属性。根据你的属性是否可序列化,以及你的组件是服务端组件还是客户端组件,你可以使用 clientProps
或 serverProps
属性来实现。
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
// highlight-line
components: {
logout: {
Button: {
path: '/src/components/Logout#MyComponent',
clientProps: {
myCustomProp: 'Hello, World!', // highlight-line
},
},
},
},
},
})
以下是你的组件接收这个属性的方式:
import React from 'react'
import { Link } from '@payloadcms/ui'
export function MyComponent({ myCustomProp }: { myCustomProp: string }) {
return <Link href="/admin/logout">{myCustomProp}</Link>
}
客户端组件
Payload 中的所有自定义组件默认都是 React Server Components,但你可以通过在文件顶部添加 'use client'
指令来使用 Client Components。Payload 会在渲染组件前自动检测并移除所有 不可序列化 的默认 props。
// highlight-start
'use client'
// highlight-end
import React, { useState } from 'react'
export function MyClientComponent() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>已点击 {count} 次</button>
)
}
提醒: 客户端组件不能传递 不可序列化的 props。如果你在 Server Component 中渲染 Client Component,请确保其 props 是可序列化的。
访问 Payload 配置
在任何 Server Component 中,都可以直接从 payload
属性访问 Payload 配置:
import React from 'react'
export default async function MyServerComponent({
payload: {
config, // highlight-line
},
}) {
return <Link href={config.serverURL}>Go Home</Link>
}
但是,Payload 配置在设计上是 不可序列化的。它包含大量自定义验证函数等。这意味着 Payload 配置无法直接完整传递给 Client Components。
为此,Payload 会创建一个 Client Config 并传递给 Config Provider。这是一个可序列化的 Payload 配置版本,可以通过 useConfig
钩子在任意 Client Component 中访问:
'use client'
import React from 'react'
import { useConfig } from '@payloadcms/ui'
export function MyClientComponent() {
// highlight-start
const {
config: { serverURL },
} = useConfig()
// highlight-end
return <Link href={serverURL}>Go Home</Link>
}
更多详情请参阅 使用钩子。
同样,所有 字段组件 都会通过 props 自动接收它们各自的字段配置。
在 Server Components 中,这个 prop 名为 field
:
import React from 'react'
import type { TextFieldServerComponent } from 'payload'
export const MyClientFieldComponent: TextFieldServerComponent = ({
field: { name },
}) => {
return <p>{`This field's name is ${name}`}</p>
}
在 Client Components 中,这个 prop 名为 clientField
,因为其中不可序列化的 props 已被移除:
'use client'
import React from 'react'
import type { TextFieldClientComponent } from 'payload'
export const MyClientFieldComponent: TextFieldClientComponent = ({
clientField: { name },
}) => {
return <p>{`This field's name is ${name}`}</p>
}
获取当前语言
所有自定义组件都可以支持语言翻译,以保持与 Payload 的 I18n 一致。这将使你的自定义组件能够根据用户偏好显示正确的语言。
要实现这一点,首先需要将你的翻译资源添加到 I18n Config。然后,在任何 Server Component 中,你可以使用 @payloadcms/translations
中的 getTranslation
函数来翻译资源。
默认情况下,所有 Server Component 都会自动接收 i18n
对象作为 prop:
import React from 'react'
import { getTranslation } from '@payloadcms/translations'
export default async function MyServerComponent({ i18n }) {
const translatedTitle = getTranslation(myTranslation, i18n) // highlight-line
return <p>{translatedTitle}</p>
}
在 Client Component 中实现这一功能的最佳方式是使用 @payloadcms/ui
中的 useTranslation
钩子:
'use client'
import React from 'react'
import { useTranslation } from '@payloadcms/ui'
export function MyClientComponent() {
const { t, i18n } = useTranslation() // highlight-line
return (
<ul>
<li>{t('namespace1:key', { variable: 'value' })}</li>
<li>{t('namespace2:key', { variable: 'value' })}</li>
<li>{i18n.language}</li>
</ul>
)
}
查看 Hooks 文档获取可用钩子的完整列表。
获取当前语言环境
所有自定义视图都可以支持多语言环境,以与 Payload 的本地化功能保持一致。这可以用于限定 API 请求范围等。
所有 Server Components 默认会自动接收 locale
对象作为 prop:
import React from 'react'
export default async function MyServerComponent({ payload, locale }) {
const localizedPage = await payload.findByID({
collection: 'pages',
id: '123',
locale,
})
return <p>{localizedPage.title}</p>
}
在 Client Component 中实现这一功能的最佳方式是使用 @payloadcms/ui
提供的 useLocale
钩子:
'use client'
import React from 'react'
import { useLocale } from '@payloadcms/ui'
function Greeting() {
const locale = useLocale() // highlight-line
const trans = {
en: 'Hello',
es: 'Hola',
}
return <span>{trans[locale.code]}</span>
}
完整可用钩子列表请参阅钩子文档。
使用钩子
为了更便捷地构建自定义组件,你可以在任何 Client Component 中使用Payload 内置的 React 钩子。例如,你可能需要与 Payload 的某个 React Context 进行交互。为此,你可以根据需求选择使用众多可用钩子中的一个。
'use client'
import React from 'react'
import { useDocumentInfo } from '@payloadcms/ui'
export function MyClientComponent() {
const { slug } = useDocumentInfo() // highlight-line
return <p>{`实体 slug: ${slug}`}</p>
}
完整可用钩子列表请参阅钩子文档。
添加样式
Payload 提供了一个强大的 CSS 库,可用于为自定义组件添加样式,使其与 Payload 内置样式保持一致。这将确保你的自定义组件能很好地集成到现有设计系统中,并自动适应可能发生的主题变化。
要应用自定义样式,只需将你的 .css
或 .scss
文件导入到自定义组件中:
import './index.scss'
export function MyComponent() {
return <div className="my-component">My Custom Component</div>
}
例如,要为自定义组件背景添加颜色,可以使用以下 CSS:
.my-component {
background-color: var(--theme-elevation-500);
}
Payload 还导出了其 SCSS 库以供复用,其中包含 mixins 等。要使用这些功能,只需在你的 .scss
文件中按如下方式导入:
@import '~@payloadcms/ui/scss';
.my-component {
@include mid-break {
background-color: var(--theme-elevation-900);
}
}
注意: 你也可以深入使用 Payload 自身的组件样式,或轻松应用全局 CSS。更多信息请参阅此处。