渲染列表
🌟 数组到UI的转换
🔄 基本思路:数组转组件
- 🧩 核心理念:将数据和UI分离,通过JavaScript数组方法动态生成组件
- 📊 常见场景:显示用户列表、商品卡片、评论、菜单项等
- 🛠️ 主要工具:JavaScript的
map()
和filter()
方法
🗂️ 使用map()方法渲染列表
🔍 基本步骤
- 📦 存储数据:将数据放入数组中
- 🔄 转换数据:使用
map()
方法将数据项转换为JSX元素 - 📄 返回结果:将生成的JSX元素数组包含在父元素中返回
jsx
// 基本示例:渲染人名列表
const people = [
'凯瑟琳·约翰逊: 数学家',
'马里奥·莫利纳: 化学家',
'穆罕默德·阿卜杜勒·萨拉姆: 物理学家',
];
function PeopleList() {
// 使用map()转换数据为JSX元素
const listItems = people.map(person =>
<li>{person}</li>
);
// 返回包含所有列表项的ul元素
return <ul>{listItems}</ul>;
}
🎯 注意事项
- 📝 每个列表项需要唯一key:避免"Each child in a list should have a unique key"警告
- 🔄 JSX表达式直接使用:生成的JSX数组可以直接在其他JSX中使用
- 🧠 两步法或内联法:可以先生成数组再使用,也可以直接在JSX内联使用
map()
🔍 使用filter()方法筛选列表项
🧹 数据过滤技巧
- 🔎 选择性渲染:只显示符合特定条件的列表项
- 🔄 链式调用:可以先
filter()
后map()
,组合使用多个数组方法 - 🧮 高效处理:减少渲染的项数,提高性能
jsx
// 筛选并渲染化学家列表
const people = [
{ name: '凯瑟琳·约翰逊', profession: '数学家' },
{ name: '马里奥·莫利纳', profession: '化学家' },
{ name: '穆罕默德·阿卜杜勒·萨拉姆', profession: '物理学家' },
{ name: '珀西·莱温·朱利亚', profession: '化学家' },
];
function ChemistsList() {
// 1. 先过滤出化学家
const chemists = people.filter(person =>
person.profession === '化学家'
);
// 2. 将过滤后的数组转换为JSX元素
const listItems = chemists.map(person =>
<li key={person.name}>{person.name}</li>
);
return <ul>{listItems}</ul>;
}
🔑 理解和使用key
🏷️ 为什么需要key?
- 🔄 元素身份识别:让React能够识别哪些项被添加、删除或重新排序
- 🎯 精确更新:避免整个列表重新渲染,只更新变化的元素
- 🛡️ 维持状态:保持组件状态,防止在重新渲染时丢失(如表单输入)
📌 key的正确使用
- 🆔 唯一性:在兄弟元素之间必须唯一,不同列表间可以重复
- 🔒 稳定性:key不应该随着时间或重新渲染而改变
- 🧩 数据ID优先:尽量使用来自数据的唯一标识符作为key
jsx
// ✅ 正确使用key的例子
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
</li>
))}
</ul>
);
}
⚠️ 避免的错误做法
- 🚫 不要使用索引作为key:当列表项可能重新排序时,会导致意外问题jsx
// 🔴 不好的做法 {todos.map((todo, index) => ( <li key={index}>...</li> ))}
- 🚫 不要动态生成key:不要使用
key={Math.random()}
等方式随机生成keyjsx// 🔴 错误做法 <li key={Math.random()}>...</li>
- 🚫 不要将key当作props传递:key仅供React内部使用,组件不会接收到key作为prop
🛠️ 正确设置key的方法
- 💽 数据库ID:使用数据库中的主键或唯一标识符
- 📝 本地生成ID:使用
crypto.randomUUID()
或类似库生成的ID - 📊 复合键:如果没有单一ID,可以组合多个字段创建唯一标识符
jsx
// 不同来源数据的key设置
// 1. 来自数据库的数据
<li key={user.id}>{user.name}</li>
// 2. 本地生成的数据
const [todos, setTodos] = useState([]);
function addTodo(text) {
setTodos([...todos, {
id: crypto.randomUUID(),
text
}]);
}
🧩 处理嵌套列表
📦 列表中的列表
- 🔄 扁平化结构:尽可能将嵌套数据扁平化,便于渲染和管理
- 🧠 分治思想:将复杂的嵌套列表拆分为单独的组件处理
- 🔑 复合key:在嵌套结构中确保key在各层级都是唯一的
jsx
// 处理二维列表:类别和类别内的项目
function NestedLists({ categories }) {
return (
<div>
{categories.map(category => (
<section key={category.id}>
<h2>{category.name}</h2>
<ul>
{category.items.map(item => (
<li key={`${category.id}-${item.id}`}>
{item.name}
</li>
))}
</ul>
</section>
))}
</div>
);
}
🧱 组件化列表项
- 📦 抽离组件:将复杂的列表项逻辑提取为独立组件
- 🔄 提高可维护性:独立组件便于测试和修改
- 🧠 关注点分离:将数据处理与UI渲染分开
jsx
// 将列表项抽离为独立组件
function PersonItem({ person }) {
return (
<li>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}</b>
{' - '}
{person.profession}
</p>
</li>
);
}
function PeopleList({ people }) {
return (
<ul>
{people.map(person => (
<PersonItem
key={person.id}
person={person}
/>
))}
</ul>
);
}
💡 高级技巧与模式
🛠️ 高效渲染大列表
- 🔄 虚拟列表:只渲染视口内可见的部分(使用
react-window
或react-virtualized
) - 🧩 分页加载:一次只渲染有限数量的项目
- 🔍 懒加载:结合
IntersectionObserver
实现滚动加载
🎨 灵活的列表展示
- 📊 网格vs列表:根据同一数据源动态切换不同展示方式
- 🔄 排序和过滤:提供用户交互控制列表顺序和筛选条件
- 🔍 搜索功能:结合列表渲染实现实时搜索结果
jsx
// 结合排序和过滤的列表
function SortableList({ items, filter, sortBy }) {
// 1. 过滤
const filteredItems = items.filter(item =>
item.category === filter || filter === 'all'
);
// 2. 排序
const sortedItems = [...filteredItems].sort((a, b) =>
sortBy === 'name' ? a.name.localeCompare(b.name) : a.id - b.id
);
// 3. 转换为JSX
return (
<ul>
{sortedItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
📝 总结
- 📋 列表渲染核心:使用
map()
将数组转换为JSX元素集合 - 🔍 选择性渲染:使用
filter()
筛选需要显示的数据 - 🔑 key的重要性:
- 确保列表元素的稳定身份识别
- 必须在兄弟元素间唯一
- 应基于稳定的数据ID,而非索引或随机值
- 🧩 组件化思想:将复杂列表项抽离为独立组件,提高代码可维护性
- 🔄 数据处理流程:数据准备(filter/sort) → 转换为JSX(map) → 渲染
- ⚠️ 常见陷阱:避免索引作为key、不要动态生成key、不将key误认为普通prop