前端 + AI 进阶 Day 1:初识流式渲染与虚拟列表
前端 + AI 进阶学习路线|Week 1-2:流式体验优化
Day 1:初识流式渲染与虚拟列表
学习时间:2025年12月25日(星期四)
关键词:流式渲染、虚拟列表、性能优化、React Virtual
🎯 今日学习目标
- 理解什么是“流式体验”及其在 AI 聊天场景中的重要性
- 掌握虚拟列表(Virtual List)的基本原理
- 使用
react-virtual实现一个基础的高性能长列表
💡 为什么需要“流式体验优化”?
在 AI 聊天应用中,用户往往会与模型进行多轮对话,消息历史可能迅速增长到 数百甚至上千条。如果直接将所有消息渲染到 DOM 中:
- 页面会变得极其卡顿(尤其在低端设备上)
- 内存占用高,滚动体验差
- 无法支持“打字机式”的逐字输出(流式响应)
因此,我们需要通过 虚拟列表 + 流式渲染 + 交互控制 三大技术,构建流畅、响应迅速的 AI 对话界面。
📚 核心概念:虚拟列表(Virtual List)
什么是虚拟列表?
虚拟列表(也称“窗口化列表”)是一种只渲染可视区域内容的优化技术。例如:列表有 1000 条消息,但屏幕一次只能看到 20 条,那么只渲染这 20 条 + 少量缓冲项,其余内容用空白占位。
优势:
- 内存占用低(DOM 节点少)
- 滚动流畅(无需重排大量元素)
- 支持平滑滚动和快速跳转
常用库:
- React:
react-virtual(轻量、Hooks 友好)、react-window - Vue:
vue-virtual-scroller
今天我们将使用
react-virtual,因其 API 简洁、与现代 React 开发范式契合。
🔧 动手实践:用 react-virtual 实现 1000+ 条消息的聊天列表
步骤 1:创建 React 项目(若未创建)
npx create-react-app day01-virtual-list
cd day01-virtual-list
npm install react-virtual
步骤 2:编写虚拟聊天列表组件
// src/components/VirtualChatList.jsx
import { useVirtual } from 'react-virtual';
import { useRef } from 'react';
const VirtualChatList = ({ messages }) => {
const parentRef = useRef(null);
// 固定每条消息高度(简化实现)
const rowHeight = 60;
const virtualizer = useVirtual({
size: messages.length,
parentRef,
estimateSize: () => rowHeight, // 修正:原模板误写为 rowTime
});
return (
<div
ref={parentRef}
style={{
height: '600px',
overflow: 'auto',
position: 'relative',
border: '1px solid #e8e8e8',
borderRadius: '8px',
padding: '8px',
}}
>
{/* 总体占位,撑起滚动条 */}
<div
style={{
height: `${virtualizer.totalSize}px`,
width: '100%',
position: 'relative',
}}
>
{virtualizer.virtualItems.map((virtualRow) => {
const message = messages[virtualRow.index];
return (
<div
key={virtualRow.index}
ref={virtualRow.measureRef}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualRow.start}px)`,
height: `${rowHeight}px`,
padding: '12px 16px',
boxSizing: 'border-box',
display: 'flex',
alignItems: 'center',
borderBottom: '1px solid #f0f0f0',
backgroundColor: message.role === 'user' ? '#e6f7ff' : '#f6ffed',
color: message.role === 'user' ? '#1890ff' : '#52c41a',
fontWeight: '500',
}}
>
{message.role === 'user' ? '👤 用户' : '🤖 AI'}: {message.content}
</div>
);
})}
</div>
</div>
);
};
export default VirtualChatList;
步骤 3:在 App 中使用
// src/App.jsx
import { useMemo } from 'react';
import VirtualChatList from './components/VirtualChatList';
function App() {
// 模拟 1000 条消息
const messages = useMemo(() => {
return Array.from({ length: 1000 }, (_, i) => ({
id: i,
role: i % 2 === 0 ? 'user' : 'assistant',
content: `这是第 ${i + 1} 条消息的内容... 欢迎使用高性能虚拟列表!`,
}));
}, []);
return (
<div style={{ padding: '20px', fontFamily: 'Inter, -apple-system, sans-serif', maxWidth: '800px', margin: '0 auto' }}>
<h1 style={{ textAlign: 'center', fontSize: '24px', fontWeight: '700', color: '#333' }}>
🚀 AI 聊天(1000+ 条消息)
</h1>
<p style={{ textAlign: 'center', color: '#666', marginBottom: '20px' }}>
虚拟列表实现高性能渲染
</p>
<VirtualChatList messages={messages} />
<div style={{ marginTop: '24px', padding: '16px', backgroundColor: '#f9f9f9', borderRadius: '12px', fontSize: '14px' }}>
<strong>✅ 验证方法:</strong>
<ul style={{ marginTop: '8px', paddingLeft: '20px' }}>
<li>打开浏览器开发者工具 → Elements 面板</li>
<li>观察 DOM 节点数量:应为 <strong>20~30 个</strong>,而非 1000+</li>
<li>快速滚动到底部再回顶部,<strong>无卡顿</strong></li>
</ul>
</div>
</div>
);
}
export default App;
✅ 效果验证
- 打开浏览器开发者工具,查看 Elements 面板:DOM 节点只有 20~30 个,而不是 1000+
- 滚动流畅,无卡顿
- 用户/AI 消息背景色区分,提升可读性
🤔 思考与延伸
-
动态高度支持:如果消息内容高度不固定(比如有的带代码块、表格),如何优化
estimateSize?
→ 可使用measureElement代替estimateSize,让每个元素自行测量高度(Day 3 将升级) -
自动滚动到底部:在聊天场景中,新消息到达时如何自动滚动到最新位置?
→ 需结合scrollIntoView或监听容器滚动位置(Day 3 实现) -
性能边界:虚拟列表在极端场景(如 10 万条消息)下表现如何?
→ 依然高效,因为 DOM 节点数与总消息数无关,只与可视区域相关
提示:
react-virtual支持measureElement和动态高度,我们将在后续几天深入。
📅 明日预告
Day 2:流式 Markdown 渲染入门
- 使用
react-markdown+rehype-highlight实现代码高亮 - 支持表格、数学公式(KaTeX)
- 构建可逐步追加内容的流式渲染器
✍️ 小结
今天,我们迈出了流式体验优化的第一步:用虚拟列表解决长列表性能问题。这是构建高性能 AI 聊天界面的基石。通过只渲染可视区域,我们让 1000+ 条消息的聊天界面依然丝滑流畅。接下来,我们将让内容“动起来”——实现真正的流式输出。
💬 实践提示:如果遇到滚动偏移或高度计算不准,可适当增大
overscan值(默认为 1),例如useVirtual({ ..., overscan: 5 })。欢迎在评论区留下你的实践心得,或提出疑问!

浙公网安备 33010602011771号