React中含大量子项的少分类列表虚拟化:Emoji选择器优化问询
解决Emoji选择器整体虚拟化的方案
核心思路是将分类标题与过滤后的Emoji打平为统一的列表项数组,对这个完整数组做整体虚拟化,实现全局统一滚动,同时只渲染可见区域内的内容。
具体实现步骤
1. 扁平化处理数据
在父组件中,根据当前搜索条件,把分类标题和符合条件的Emoji合并成带类型标识的数组,方便后续区分渲染:
// 生成扁平化的可渲染列表 const getFlattenedEmojiList = () => { const result = []; emojiSet.forEach(category => { // 添加分类标题项 result.push({ type: 'category', name: category.Name }); // 过滤符合搜索条件的Emoji并添加 const filteredEmojis = category.Icons.filter(emoji => emoji.Names.some(name => name.includes(searchQuery.toLowerCase())) ); filteredEmojis.forEach(emoji => { result.push({ type: 'emoji', data: emoji }); }); }); return result; }; // 根据搜索条件实时生成扁平化列表 const flattenedEmojiList = getFlattenedEmojiList();
2. 使用react-window实现整体虚拟化
用VariableSizeList(适配分类标题与Emoji的高度差)包裹整个列表,实现全局滚动与按需渲染:
import { VariableSizeList as List } from 'react-window'; // 定义每个列表项的高度(根据实际样式调整数值) const getItemHeight = (index) => { const item = flattenedEmojiList[index]; return item.type === 'category' ? 40 : 32; // 标题40px,Emoji项32px }; // 渲染单个列表项 const RenderEmojiItem = ({ index, style }) => { const item = flattenedEmojiList[index]; if (item.type === 'category') { return ( <div style={style} className="emoji-category-title"> <p>{item.name}</p> </div> ); } return ( <div style={style} className="emoji-item"> <Icon url={item.data.Url} /> </div> ); };
3. 替换原有渲染逻辑
把原来的#icons容器替换为虚拟化列表:
<div id="icons" style={{ height: '400px', overflow: 'auto' }}> <List width="100%" height={400} // 容器高度,可根据需求自定义 itemCount={flattenedEmojiList.length} itemSize={getItemHeight} overscanCount={5} // 提前渲染可见区域外的5个项,优化滚动流畅度 > {RenderEmojiItem} </List> </div>
4. 调整样式适配
拆分原有的.emoji-category样式,给分类标题和Emoji项分别设置样式,保证布局与原有效果一致:
.emoji-category-title { padding: 8px 12px; border-bottom: 1px solid #eee; font-weight: bold; } .emoji-item { display: inline-block; padding: 4px; cursor: pointer; }
方案优势
- 解决了单独虚拟化分类导致的多滚动容器问题,实现全局统一滚动
- 对所有小项(标题+每个Emoji)做虚拟化,直接解决数千个Emoji的渲染性能问题
- 搜索条件变化时,扁平化列表会自动更新,react-window会同步刷新可见区域内容
内容的提问来源于stack exchange,提问作者Gradyn Wursten
相关产品推荐
相关产品推荐

