在列表页、卡片流、表格单元格等场景中,后端返回的文本长度往往不可控。如果任由长文本撑开容器,页面布局就会错乱;如果简单粗暴地隐藏溢出部分,用户又不知道内容被截断了。最友好的做法是:限制显示字数,超出部分用省略号代替。本文将介绍在 React 中实现这一效果的几种常用方案,并分析各自的适用场景。

方案一:纯 CSS 实现单行与多行省略
这是性能最好、成本最低的方式。单行截断只需要三行 CSS:
.ellipsis {
overflow: hidden; /* 隐藏溢出内容 */
white-space: nowrap; /* 禁止换行 */
text-overflow: ellipsis; /* 溢出部分显示省略号 */
}
需要注意的是,text-overflow: ellipsis 必须配合 overflow: hidden 和 white-space: nowrap 一起使用,三者缺一不可。少写任何一条,省略号都不会生效,这是新手最常踩的坑。
多行截断要依赖 WebKit 私有属性,但在现代浏览器(包括 Firefox)中已经得到广泛支持:
.ellipsis-multi {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2; /* 限制显示两行 */
overflow: hidden;
}
在 React 中使用时,可以把这些样式写成全局 CSS 类,也可以配合 CSS Modules 或 styled-components 引入。由于整个截断过程由浏览器渲染引擎完成,不占用任何 JavaScript 计算资源,即使列表中渲染上千条数据也不会带来性能负担。
CSS 方案的局限在于:它只能按行数截断,无法精确控制字符个数;而且截断后的完整文本如果需要参与其他逻辑(比如复制、搜索),CSS 是拿不到的。这时就需要 JavaScript 方案。
方案二:JavaScript 按字数截取字符串
如果需求是「最多显示 N 个字符」,就需要在渲染前对字符串做截取处理。核心逻辑很简单:
function truncate(text, maxLength) {
if (!text) return '';
if (text.length <= maxLength) return text;
return text.slice(0, maxLength) + '...';
}
// 在组件中使用
function Title({ content }) {
return <span>{truncate(content, 20)}</span>;
}
这个函数在每次渲染时都会执行,对于长列表可以考虑用 useMemo 缓存结果,避免重复计算。另外,如果列表数据来自接口,更推荐在数据到达时就完成截取,把处理逻辑收敛在数据层,让渲染层保持纯净。
中英文混排是一个容易被忽视的细节。中文一个字符占两倍宽度,直接按 length 截断会导致英文长单词和中文短句视觉长度不一致。可以借助正则对全角字符加权:
function truncateByWidth(text, maxWidth) {
let width = 0;
let result = '';
for (const char of text) {
// 全角字符按 2 计算,半角按 1 计算
width += /[\u4e00-\u9fa5\u3000-\u303f\uff00-\uffef]/.test(char) ? 2 : 1;
if (width > maxWidth) return result + '...';
result += char;
}
return result;
}
这种按视觉宽度截断的方式在表格、标签等对齐要求高的场景中效果明显更好。需要注意的是,emoji 由多个码位组成,使用 for...of 遍历字符串可以正确处理代理对,避免出现截断后乱码的问题。
方案三:封装可复用的 EllipsisText 组件
实际项目中,截断逻辑往往还需要附带交互,例如鼠标悬停时用 Tooltip 显示完整文本。把逻辑封装成组件是更工程化的做法:
import { useState } from 'react';
import PropTypes from 'prop-types';
function EllipsisText({ text, maxLength, showTooltip }) {
const [hovered, setHovered] = useState(false);
const isTruncated = text && text.length > maxLength;
const display = isTruncated ? text.slice(0, maxLength) + '...' : text;
return (
<span
style={{ cursor: isTruncated ? 'pointer' : 'default' }}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
title={showTooltip ? text : undefined}
>
{display}
</span>
);
}
EllipsisText.propTypes = {
text: PropTypes.string.isRequired,
maxLength: PropTypes.number,
showTooltip: PropTypes.bool,
};
EllipsisText.defaultProps = {
maxLength: 50,
showTooltip: true,
};
export default EllipsisText;
使用 title 属性是最轻量的 Tooltip 方案,浏览器原生支持,无需引入额外依赖。如果项目已经使用了 Ant Design 等组件库,可以把 span 替换为 Typography.Text,它内置了 ellipsis 属性,还支持展开收起、尾随操作按钮等高级能力。
封装组件的好处在于统一了交互规范。当产品后续提出「截断文本需要支持点击展开」的需求时,只需要修改一个组件,所有引用处自动生效,避免了散落在各处的复制粘贴代码带来的维护噩梦。
方案对比与选型建议
三种方案没有绝对优劣,关键看业务场景。下表总结了它们的主要差异:
| 维度 | CSS 截断 | JS 字符串截取 | 组件封装 |
|---|---|---|---|
| 性能 | 最优,零 JS 开销 | 每次渲染需计算 | 取决于内部实现 |
| 精确控制字数 | 不支持,只能按行数 | 支持 | 支持 |
| 完整文本交互 | 需额外取原始数据 | 天然持有完整文本 | 天然持有完整文本 |
| 多行截断 | 原生支持 | 需按宽度估算 | 可组合 CSS 实现 |
| 复用性 | 靠样式类复用 | 靠工具函数复用 | 最高,开箱即用 |
如果只是简单的排版需求,优先选 CSS 方案,简单可靠;如果产品要求严格按字数截断(比如摘要展示固定 100 字),用 JS 截取并在数据层处理;如果是中大型项目且交互复杂,封装组件是最值得投入的方式。
最后提醒一点关于可访问性的实践:截断后的文本对屏幕阅读器并不友好,建议在元素上保留完整的 aria-label 或使用视觉隐藏元素承载全文,确保辅助技术能够读到完整内容。这个小细节在 To B 项目验收中越来越常见,提前处理可以省去不少返工。
React 文本截断CSS text-overflowReact 组件封装修改时间:2026-09-02 03:32:36