本文说明如何封装一个可复用的代码展示组件:在卡片或详情中展示代码片段,并同时支持 语法着色 、行号 ,以及按业务标出的 单行或多行背景高亮(如"问题行"),并给出接入方式。
### 文章目录
- [@[TOC]](#文章目录 @[TOC] 一、组件要解决什么问题 二、能力与依赖概览 三、Props 设计 四、语法高亮与行号 五、错误行(可多行)高亮 六、布局与滚动 七、接入方式(How to integrate) 1. 安装依赖 2. 放置组件 3. 在业务中使用 八、完整组件代码)
- [一、组件要解决什么问题](#文章目录 @[TOC] 一、组件要解决什么问题 二、能力与依赖概览 三、Props 设计 四、语法高亮与行号 五、错误行(可多行)高亮 六、布局与滚动 七、接入方式(How to integrate) 1. 安装依赖 2. 放置组件 3. 在业务中使用 八、完整组件代码)
- [二、能力与依赖概览](#文章目录 @[TOC] 一、组件要解决什么问题 二、能力与依赖概览 三、Props 设计 四、语法高亮与行号 五、错误行(可多行)高亮 六、布局与滚动 七、接入方式(How to integrate) 1. 安装依赖 2. 放置组件 3. 在业务中使用 八、完整组件代码)
- [三、Props 设计](#文章目录 @[TOC] 一、组件要解决什么问题 二、能力与依赖概览 三、Props 设计 四、语法高亮与行号 五、错误行(可多行)高亮 六、布局与滚动 七、接入方式(How to integrate) 1. 安装依赖 2. 放置组件 3. 在业务中使用 八、完整组件代码)
- [四、语法高亮与行号](#文章目录 @[TOC] 一、组件要解决什么问题 二、能力与依赖概览 三、Props 设计 四、语法高亮与行号 五、错误行(可多行)高亮 六、布局与滚动 七、接入方式(How to integrate) 1. 安装依赖 2. 放置组件 3. 在业务中使用 八、完整组件代码)
- [五、错误行(可多行)高亮](#文章目录 @[TOC] 一、组件要解决什么问题 二、能力与依赖概览 三、Props 设计 四、语法高亮与行号 五、错误行(可多行)高亮 六、布局与滚动 七、接入方式(How to integrate) 1. 安装依赖 2. 放置组件 3. 在业务中使用 八、完整组件代码)
- [六、布局与滚动](#文章目录 @[TOC] 一、组件要解决什么问题 二、能力与依赖概览 三、Props 设计 四、语法高亮与行号 五、错误行(可多行)高亮 六、布局与滚动 七、接入方式(How to integrate) 1. 安装依赖 2. 放置组件 3. 在业务中使用 八、完整组件代码)
- [七、接入方式(How to integrate)](#文章目录 @[TOC] 一、组件要解决什么问题 二、能力与依赖概览 三、Props 设计 四、语法高亮与行号 五、错误行(可多行)高亮 六、布局与滚动 七、接入方式(How to integrate) 1. 安装依赖 2. 放置组件 3. 在业务中使用 八、完整组件代码)
- [1. 安装依赖](#文章目录 @[TOC] 一、组件要解决什么问题 二、能力与依赖概览 三、Props 设计 四、语法高亮与行号 五、错误行(可多行)高亮 六、布局与滚动 七、接入方式(How to integrate) 1. 安装依赖 2. 放置组件 3. 在业务中使用 八、完整组件代码)
- [2. 放置组件](#文章目录 @[TOC] 一、组件要解决什么问题 二、能力与依赖概览 三、Props 设计 四、语法高亮与行号 五、错误行(可多行)高亮 六、布局与滚动 七、接入方式(How to integrate) 1. 安装依赖 2. 放置组件 3. 在业务中使用 八、完整组件代码)
- [3. 在业务中使用](#文章目录 @[TOC] 一、组件要解决什么问题 二、能力与依赖概览 三、Props 设计 四、语法高亮与行号 五、错误行(可多行)高亮 六、布局与滚动 七、接入方式(How to integrate) 1. 安装依赖 2. 放置组件 3. 在业务中使用 八、完整组件代码)
- [八、完整组件代码](#文章目录 @[TOC] 一、组件要解决什么问题 二、能力与依赖概览 三、Props 设计 四、语法高亮与行号 五、错误行(可多行)高亮 六、布局与滚动 七、接入方式(How to integrate) 1. 安装依赖 2. 放置组件 3. 在业务中使用 八、完整组件代码)
一、组件要解决什么问题
在只读展示场景里,常见需求包括:
- 代码需要 语法高亮 ,而不是纯
<pre><code>灰底黑字。 - 需要 行号 ,便于和规则说明、接口返回的
lineNumber等对照。 - 需要把 一处或多处行 用背景色标出(例如静态分析命中行、评审关注行)。
把这些能力收拢到一个组件里,业务侧只关心:标题、代码字符串、语言(可选)、要高亮的行号(可选、可多行)。
二、能力与依赖概览
| 能力 | 实现思路 |
|---|---|
| 语法高亮 + 行号 | react-syntax-highlighter 的 Prism 分支 |
| 主题 | 例如 oneDark(可替换为任意 Prism 主题) |
| 错误行高亮 | SyntaxHighlighter 的 lineProps 按行注入 style |
| 标题区 + 复制(可选) | 标题栏内嵌复制按钮组件 |
依赖:
bash
pnpm add react-syntax-highlighter
pnpm add -D @types/react-syntax-highlighter
(使用 npm:npm install react-syntax-highlighter;使用 yarn:yarn add react-syntax-highlighter。TypeScript 类型包同理加 -D。)
三、Props 设计
建议接口与含义如下(命名可按项目习惯调整):
| Prop | 类型 | 说明 |
|---|---|---|
title |
string |
区块标题,如 "xxx Code" |
code |
`string | undefined` |
titleClassName |
string |
标题栏 Tailwind(或任意)类名,用于区分不同区块样式 |
language |
string(可选) |
传给 Prism 的语言名,如 javascript/cobol;不支持时可改用 text 等兜底 |
highlightLine |
`number | readonly number[]`(可选) |
行号约定: showLineNumbers 开启后,第一行对应 lineNumber === 1,与 highlightLine 比较时保持一致。若后端给的是文件绝对行号,而 code 只是片段,需要先在业务层把绝对行号换算成「片段内行号」,再传入组件。
四、语法高亮与行号
使用 Prism 分支与主题:
ts
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
<SyntaxHighlighter
language={language}
style={oneDark}
customStyle={{ margin: 0, borderRadius: 0, fontSize: '12px' }}
showLineNumbers
wrapLines
lineProps={...}
>
{code || ''}
</SyntaxHighlighter>
要点:
showLineNumbers:打开行号列。customStyle:去掉默认外边距、与外层圆角衔接、统一字号。wrapLines:长行在容器内换行;若希望严格横向滚动、不换行,可关闭并在外层加overflow-x-auto(按产品设计选择)。
五、错误行(可多行)高亮
核心是用 lineProps :库会对每一行调用一次,传入当前行号(1-based),返回该行的 DOM 属性(通常只改 style)。
判断逻辑示例:
ts
function isHighlightedLine(lineNumber: number): boolean {
if (highlightLine === undefined)
return false
if (typeof highlightLine === 'number')
return lineNumber === highlightLine
return highlightLine.includes(lineNumber)
}
配合:
tsx
lineProps={(lineNumber: number) => ({
style: isHighlightedLine(lineNumber)
? { display: 'block', backgroundColor: 'rgba(255, 107, 107, 0.2)' }
: { display: 'block' },
})}
注意:
display: 'block'与wrapLines组合时,可避免部分主题下行渲染异常;若你用的版本无此问题,可简化为只设置高亮行的backgroundColor。- 多行高亮时,数组内行号不要重复;
undefined与片段外行号不会产生高亮。
六、布局与滚动
典型结构:标题栏 + 代码区滚动容器。
- 标题栏:
flex+justify-between,左侧标题、右侧复制按钮(若有)。 - 代码区:
max-height(如300px)+overflow-y-auto,避免整页被长代码撑开。 - 外层若放在两栏栅格里,注意给代码容器
min-width: 0或合理overflow,避免栅格把内部撑破(按你使用的 CSS 框架调整)。
七、接入方式(How to integrate)
1. 安装依赖
bash
pnpm add react-syntax-highlighter
pnpm add -D @types/react-syntax-highlighter
2. 放置组件
例如放在 src/components/highlight-code-block.tsx,导出 HighlightCodeBlock。
3. 在业务中使用
仅语法高亮 + 行号,无错误行:
ts
<HighlightCodeBlock
title="Snippet"
code={source}
titleClassName="bg-neutral-800 text-neutral-200"
/>
单行错误行:
ts
<HighlightCodeBlock
title="Original"
code={source}
titleClassName="..."
highlightLine={10}
/>
多行错误行:
ts
<HighlightCodeBlock
title="Original"
code={source}
titleClassName="..."
highlightLine={[2, 3, 7]}
/>
并列两个区块 (例如左右对照):各自传独立的 title、titleClassName、code,highlightLine 可只对一侧传入。
八、完整组件代码
ts
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
import { CopyCodeButton } from '@/components/copy-code-button'
interface HighlightCodeBlockProps {
title: string
code?: string
titleClassName: string
language?: string
/** 单行高亮传 `number`;多行传 `number[]`(均为 1-based 行号,与展示行号一致) */
highlightLine?: number | readonly number[]
}
export function HighlightCodeBlock({
title,
code,
titleClassName,
language = 'cobol',
highlightLine,
}: HighlightCodeBlockProps) {
function isHighlightedLine(lineNumber: number): boolean {
if (highlightLine === undefined)
return false
if (typeof highlightLine === 'number')
return lineNumber === highlightLine
return highlightLine.includes(lineNumber)
}
return (
<div>
<div className={`p-2 text-xs font-semibold tracking-wide rounded-t-lg flex items-center justify-between gap-2 ${titleClassName}`}>
<span>{title}</span>
<CopyCodeButton value={code} label="Copy" />
</div>
<div className="rounded-b-lg overflow-hidden max-h-[300px] overflow-y-auto">
<SyntaxHighlighter
language={language}
style={oneDark}
customStyle={{ margin: 0, borderRadius: 0, fontSize: '12px' }}
showLineNumbers
wrapLines
lineProps={(lineNumber: number) => ({
style: isHighlightedLine(lineNumber)
? { display: 'block', backgroundColor: 'rgba(255, 107, 107, 0.2)' }
: { display: 'block' },
})}
>
{code || ''}
</SyntaxHighlighter>
</div>
</div>
)
}