用 react 的react-syntax-highlighter 实现语法高亮、行号与多行错误行高亮

本文说明如何封装一个可复用的代码展示组件:在卡片或详情中展示代码片段,并同时支持 语法着色行号 ,以及按业务标出的 单行或多行背景高亮(如"问题行"),并给出接入方式。

### 文章目录

  • [@[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 主题)
错误行高亮 SyntaxHighlighterlineProps 按行注入 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]}
/>

并列两个区块 (例如左右对照):各自传独立的 titletitleClassNamecodehighlightLine 可只对一侧传入。


八、完整组件代码

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>
  )
}
相关推荐
lbh2 小时前
从LLM到Agent的核心概念
前端·openai·ai编程
Irene19912 小时前
JavaScript脚本加载的两种方式:defer/async 的区别
前端·javascript·php
天若有情6732 小时前
开篇必看:零基础吃透前端,别再盲目死记硬背了
前端
RulerMike2 小时前
three 实现简单机械臂逆运动
前端·ai编程·three.js
darkb1rd2 小时前
从“会聊天”到“会搭页面”:一次 TinyEngine + MCP 的前端智能化实战思路
前端
爱喝白开水a2 小时前
春节后普通程序员如何“丝滑”跨行AI:不啃算法,也能拿走AI
java·人工智能·算法·spring·ai·前端框架·大模型
社恐的下水道蟑螂2 小时前
从奶茶店彻底搞懂 SSR!从零到拿捏服务端渲染,看完面试吹牛逼不卡壳
前端·react.js·性能优化
EnCi Zheng3 小时前
M1-如何转换为HTML
前端·html
luanma1509803 小时前
Laravel 8.X重磅特性全解析
前端·javascript·vue.js·php·lua