转换 JSX

富文本转 JSX

要将富文本转换为 JSX,请从 @payloadcms/richtext-lexical/react 导入 RichText 组件,并将富文本内容传递给它:

import React from 'react'
import { RichText } from '@payloadcms/richtext-lexical/react'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'

export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
  return <RichText data={data} />
}

RichText 组件内置了常见 Lexical 节点的转换器。你可以通过 converters 属性添加或覆盖转换器,以支持自定义块、自定义节点或任何你需要的修改。参考网站模板查看实际示例。

获取数据时,请确保你的 depth 设置足够高,以完全填充诸如上传等 Lexical 节点。JSX 转换器需要完整的数据才能正常工作。

内部链接

默认情况下,Payload 不知道如何将内部链接转换为 JSX,因为它不知道内部链接对应的 URL 是什么。当你尝试渲染包含内部链接的内容时,会在控制台看到 "found internal link, but internalDocToHref is not provided" 错误。

要解决这个问题,你需要向 LinkJSXConverter 传递 internalDocToHref 属性。这个属性是一个函数,接收链接节点并返回文档的 URL。

import type {
  DefaultNodeTypes,
  SerializedLinkNode,
} from '@payloadcms/richtext-lexical'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'

import {
  type JSXConvertersFunction,
  LinkJSXConverter,
  RichText,
} from '@payloadcms/richtext-lexical/react'
import React from 'react'

const internalDocToHref = ({ linkNode }: { linkNode: SerializedLinkNode }) => {
  const { relationTo, value } = linkNode.fields.doc!
  if (typeof value !== 'object') {
    throw new Error('Expected value to be an object')
  }
  const slug = value.slug

  switch (relationTo) {
    case 'posts':
      return `/posts/${slug}`
    case 'categories':
      return `/category/${slug}`
    case 'pages':
      return `/${slug}`
    default:
      return `/${relationTo}/${slug}`
  }
}

const jsxConverters: JSXConvertersFunction<DefaultNodeTypes> = ({
  defaultConverters,
}) => ({
  ...defaultConverters,
  ...LinkJSXConverter({ internalDocToHref }),
})

export const MyComponent: React.FC<{
  lexicalData: SerializedEditorState
}> = ({ lexicalData }) => {
  return <RichText converters={jsxConverters} data={lexicalData} />
}

Lexical 块

如果你的富文本包含自定义块(Blocks)或内联块(Inline Blocks),你必须为每个块的 slug 提供匹配的自定义转换器。这个转换器默认不包含,因为 Payload 不知道如何渲染你的自定义块。

例如:

'use client'
import type { MyInlineBlock, MyNumberBlock, MyTextBlock } from '@/payload-types'
import type {
  DefaultNodeTypes,
  SerializedBlockNode,
  SerializedInlineBlockNode,
} from '@payloadcms/richtext-lexical'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'

import {
  type JSXConvertersFunction,
  RichText,
} from '@payloadcms/richtext-lexical/react'
import React from 'react'

// 扩展默认节点类型以包含你的自定义块,确保完全类型安全
type NodeTypes =
  | DefaultNodeTypes
  | SerializedBlockNode<MyNumberBlock | MyTextBlock>
  | SerializedInlineBlockNode<MyInlineBlock>

const jsxConverters: JSXConvertersFunction<NodeTypes> = ({
  defaultConverters,
}) => ({
  ...defaultConverters,
  blocks: {
    // 每个键应该匹配你的块的 slug
    myNumberBlock: ({ node }) => <div>{node.fields.number}</div>,
    myTextBlock: ({ node }) => (
      <div style={{ backgroundColor: 'red' }}>{node.fields.text}</div>
    ),
  },
  inlineBlocks: {
    // 每个键应该匹配你的内联块的 slug
    myInlineBlock: ({ node }) => <span>{node.fields.text}</span>,
  },
})

export const MyComponent: React.FC<{
  lexicalData: SerializedEditorState
}> = ({ lexicalData }) => {
  return <RichText converters={jsxConverters} data={lexicalData} />
}

覆盖转换器

你可以通过向 converters 属性/函数传递自定义转换器(按节点类型键控)来覆盖任何默认的 JSX 转换器。

示例 - 覆盖 upload 节点转换器以使用 next/image:

'use client'
import type {
  DefaultNodeTypes,
  SerializedUploadNode,
} from '@payloadcms/richtext-lexical'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'

import {
  type JSXConvertersFunction,
  RichText,
} from '@payloadcms/richtext-lexical/react'
import Image from 'next/image'
import React from 'react'

type NodeTypes = DefaultNodeTypes

// 使用 next/image 的自定义上传转换器组件
const CustomUploadComponent: React.FC<{
  node: SerializedUploadNode
}> = ({ node }) => {
  if (node.relationTo === 'uploads') {
    const uploadDoc = node.value
    if (typeof uploadDoc !== 'object') {
      return null
    }
    const { alt, height, url, width } = uploadDoc
    return <Image alt={alt} height={height} src={url} width={width} />
  }

  return null
}

const jsxConverters: JSXConvertersFunction<NodeTypes> = ({
  defaultConverters,
}) => ({
  ...defaultConverters,
  // 覆盖默认的上传转换器
  upload: ({ node }) => {
    return <CustomUploadComponent node={node} />
  },
})

export const MyComponent: React.FC<{
  lexicalData: SerializedEditorState
}> = ({ lexicalData }) => {
  return <RichText converters={jsxConverters} data={lexicalData} />
}