在实际应用中,我们经常需要根据数组数据动态生成一组相似的元素。例如:用户列表、商品列表、评论列表等。
// 数据数组
const users = [
{ id: 1, name: '张三', age: 25 },
{ id: 2, name: '李四', age: 30 },
{ id: 3, name: '王五', age: 28 }
];
// 列表渲染
function UserList() {
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name} - {user.age}岁
</li>
))}
</ul>
);
}
JavaScript的map()函数是React中渲染列表的主要工具,它会遍历数组并为每个元素返回一个新的React元素。
const numbers = [1, 2, 3, 4, 5];
function NumberList() {
const listItems = numbers.map(number =>
<li key={number.toString()}>
{number}
</li>
);
return <ul>{listItems}</ul>;
}
const products = [
{ id: 101, name: 'iPhone', price: 6999 },
{ id: 102, name: 'MacBook', price: 12999 },
{ id: 103, name: 'iPad', price: 3299 }
];
function ProductList() {
return (
<div className="products">
{products.map(product => (
<div key={product.id} className="product">
<h3>{product.name}</h3>
<p>价格: ¥{product.price}</p>
</div>
))}
</div>
);
}
function EnhancedList() {
const items = [
{ id: 1, title: '任务1', completed: true },
{ id: 2, title: '任务2', completed: false },
{ id: 3, title: '任务3', completed: true }
];
// 使用map计算摘要
const total = items.length;
const completedCount = items.filter(item => item.completed).length;
return (
<div>
<div className="summary">
总计: {total} | 已完成: {completedCount}
</div>
{/* 使用map渲染列表 */}
<ul className="item-list">
{items.map(item => {
// 可以在这里添加复杂的逻辑
const statusClass = item.completed ? 'completed' : 'pending';
return (
<li key={item.id} className={`item ${statusClass}`}>
<span>{item.title}</span>
<span className="status">
{item.completed ? '✓ 已完成' : '○ 待完成'}
</span>
</li>
);
})}
</ul>
{/* 使用map生成选项 */}
<select>
{items.map(item => (
<option key={item.id} value={item.id}>
{item.title}
</option>
))}
</select>
</div>
);
}
Key是React用于识别列表中元素唯一性的特殊字符串属性。它帮助React跟踪哪些元素被添加、修改或删除。
Key帮助React识别元素,从而实现高效的DOM更新。当列表变化时,React可以准确地知道哪些元素需要更新,而不是重新渲染整个列表。
// 没有Key时,React需要重新渲染整个列表
// 有Key时,React只更新变化的元素
Key确保组件的状态在重新渲染时得到保持。如果没有Key,组件的状态可能会丢失或混乱。
// 输入框的值、复选框的状态等
// 在列表重新排序时能够正确保持
Key是React识别列表中每个元素的唯一标识。即使元素内容相同,Key也能帮助React区分它们。
// 相同内容的两个元素
// <li>任务1</li>
// <li>任务1</li>
// Key让React知道这是两个不同的元素
正确的Key可以确保CSS过渡和动画效果正常工作,特别是在列表重新排序时。
// 列表项添加/删除时的动画
// 列表重新排序时的过渡效果
// 都依赖于稳定的Key
| Key类型 | 示例 | 适用场景 | 注意事项 |
|---|---|---|---|
| 数据ID | key={item.id} |
有唯一ID的数据(推荐) | 最佳选择,稳定且唯一 |
| 索引 | key={index} |
静态列表、简单展示 | 不推荐,可能导致问题 |
| 组合Key | key={`${type}-${id}`} |
不同类型数据混合 | 确保组合后的唯一性 |
| 生成ID | key={nanoid()} |
没有ID的本地数据 | 每次渲染都不同,不推荐 |
| 时间戳 | key={Date.now()} |
测试、临时数据 | 每次渲染都不同,不推荐 |
// ✅ 推荐:使用数据中的唯一ID
const users = [
{ id: 'u1', name: '张三' },
{ id: 'u2', name: '李四' }
];
users.map(user => (
<div key={user.id}>{user.name}</div>
));
// ⚠️ 谨慎使用:索引作为Key(静态列表可用)
const staticItems = ['苹果', '香蕉', '橙子'];
staticItems.map((item, index) => (
<li key={index}>{item}</li>
));
// ✅ 推荐:组合Key
const mixedData = [
{ type: 'user', id: 1, name: '张三' },
{ type: 'post', id: 1, title: '文章1' }
];
mixedData.map(item => (
<div key={`${item.type}-${item.id}`}>
{item.name || item.title}
</div>
));
// ❌ 避免:使用随机Key
items.map(item => (
<div key={Math.random()}>{item.name}</div> // 每次渲染Key都不同
));
// ❌ 避免:使用不稳定的Key
items.map((item, index) => (
<div key={item.name}>{item.name}</div> // 可能重复
));
当列表有以下情况时,绝对不要使用索引作为Key:
// ❌ 错误示例:使用索引作为Key,删除项目时会导致状态混乱
function TodoList() {
const [todos, setTodos] = useState([
{ text: '学习React', completed: false },
{ text: '写文档', completed: false },
{ text: '测试代码', completed: false }
]);
const deleteTodo = (index) => {
setTodos(todos.filter((_, i) => i !== index));
};
return (
<ul>
{todos.map((todo, index) => (
<TodoItem
key={index} // ❌ 问题:删除项目后,索引会变化
todo={todo}
onDelete={() => deleteTodo(index)}
/>
))}
</ul>
);
}
// 删除第一个项目后:
// 原来索引1的项目现在变成了索引0
// 但React认为索引0还是原来的项目
// 导致状态错乱
// ✅ 正确示例:使用唯一ID作为Key
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '学习React', completed: false },
{ id: 2, text: '写文档', completed: false },
{ id: 3, text: '测试代码', completed: false }
]);
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id} // ✅ 正确:使用唯一ID
todo={todo}
onDelete={() => deleteTodo(todo.id)}
/>
))}
</ul>
);
}
// 现在删除项目时,React能够正确识别哪个项目被删除
// 其他项目的状态得到保持
// 代码将在这里显示
对于包含大量数据的列表(成千上万条),一次性渲染所有项目会导致性能问题。解决方案是使用虚拟化技术,只渲染可见区域的项目。
import { FixedSizeList as List } from 'react-window';
const Row = ({ index, style }) => (
<div style={style}>
第 {index} 行: {items[index].name}
</div>
);
function VirtualizedList() {
return (
<List
height={400} // 列表高度
width={300} // 列表宽度
itemCount={10000} // 总项目数
itemSize={35} // 每个项目高度
>
{Row}
</List>
);
}
import React, { useMemo, useCallback } from 'react';
function OptimizedList({ items, filterText, sortBy }) {
// 使用useMemo缓存过滤和排序结果
const processedItems = useMemo(() => {
console.log('处理列表数据...');
let result = [...items];
// 过滤
if (filterText) {
result = result.filter(item =>
item.name.toLowerCase().includes(filterText.toLowerCase())
);
}
// 排序
if (sortBy) {
result.sort((a, b) => {
if (sortBy === 'name') return a.name.localeCompare(b.name);
if (sortBy === 'date') return new Date(b.date) - new Date(a.date);
return 0;
});
}
return result;
}, [items, filterText, sortBy]); // 依赖项变化时重新计算
// 使用useCallback缓存事件处理函数
const handleItemClick = useCallback((id) => {
console.log('点击项目:', id);
}, []);
// 避免在渲染函数中创建新数组
const itemIds = useMemo(() =>
processedItems.map(item => item.id),
[processedItems]
);
return (
<div>
<div className="summary">
显示 {processedItems.length} / {items.length} 个项目
</div>
<ul className="optimized-list">
{processedItems.map(item => (
<MemoizedListItem
key={item.id}
item={item}
onClick={handleItemClick}
/>
))}
</ul>
</div>
);
}
// 使用React.memo优化列表项组件
const MemoizedListItem = React.memo(function ListItem({ item, onClick }) {
return (
<li onClick={() => onClick(item.id)}>
{item.name}
</li>
);
});
function NestedList() {
const categories = [
{
id: 1,
name: '电子产品',
products: [
{ id: 101, name: 'iPhone', price: 6999 },
{ id: 102, name: 'MacBook', price: 12999 }
]
},
{
id: 2,
name: '图书',
products: [
{ id: 201, name: 'React入门', price: 59 },
{ id: 202, name: 'JavaScript高级', price: 89 }
]
}
];
return (
<div className="nested-list">
{categories.map(category => (
<div key={category.id} className="category">
<h3>{category.name}</h3>
<ul className="product-list">
{category.products.map(product => (
<li key={product.id} className="product">
<span className="name">{product.name}</span>
<span className="price">¥{product.price}</span>
</li>
))}
</ul>
</div>
))}
</div>
);
}
import React, { useState } from 'react';
function DynamicList() {
const [items, setItems] = useState([
{ id: 1, text: '第一个项目' },
{ id: 2, text: '第二个项目' },
{ id: 3, text: '第三个项目' }
]);
const [newItemText, setNewItemText] = useState('');
// 添加项目
const addItem = () => {
if (!newItemText.trim()) return;
const newItem = {
id: Date.now(), // 使用时间戳作为临时ID
text: newItemText
};
setItems([...items, newItem]);
setNewItemText('');
};
// 删除项目
const removeItem = (id) => {
setItems(items.filter(item => item.id !== id));
};
// 移动项目
const moveItem = (fromIndex, toIndex) => {
const newItems = [...items];
const [removed] = newItems.splice(fromIndex, 1);
newItems.splice(toIndex, 0, removed);
setItems(newItems);
};
// 更新项目
const updateItem = (id, newText) => {
setItems(items.map(item =>
item.id === id ? { ...item, text: newText } : item
));
};
return (
<div className="dynamic-list">
<h3>动态列表操作</h3>
<div className="add-form">
<input
type="text"
value={newItemText}
onChange={(e) => setNewItemText(e.target.value)}
placeholder="输入新项目..."
onKeyPress={(e) => e.key === 'Enter' && addItem()}
/>
<button onClick={addItem}>添加</button>
</div>
<ul className="item-list">
{items.map((item, index) => (
<li key={item.id} className="list-item">
<div className="item-content">
<span className="item-index">#{index + 1}</span>
<input
type="text"
value={item.text}
onChange={(e) => updateItem(item.id, e.target.value)}
className="item-input"
/>
</div>
<div className="item-actions">
{index > 0 && (
<button onClick={() => moveItem(index, index - 1)}>
↑ 上移
</button>
)}
{index < items.length - 1 && (
<button onClick={() => moveItem(index, index + 1)}>
↓ 下移
</button>
)}
<button
onClick={() => removeItem(item.id)}
className="delete-btn"
>
删除
</button>
</div>
</li>
))}
</ul>
<div className="list-info">
<p>总项目数: {items.length}</p>
<p>提示:可以编辑、删除、移动项目</p>
</div>
</div>
);
}
React.memo优化列表项组件useMemo缓存计算密集型操作
import React, { useState, useMemo, useCallback } from 'react';
function TaskManagementPanel() {
const [tasks, setTasks] = useState([
{ id: 1, title: '完成React教程', status: 'todo', priority: 'high', assignee: '张三' },
{ id: 2, title: '修复登录Bug', status: 'in-progress', priority: 'high', assignee: '李四' },
{ id: 3, title: '编写文档', status: 'done', priority: 'medium', assignee: '王五' },
{ id: 4, title: '优化性能', status: 'todo', priority: 'low', assignee: '赵六' },
{ id: 5, title: '团队会议', status: 'in-progress', priority: 'medium', assignee: '张三' }
]);
const [filterStatus, setFilterStatus] = useState('all');
const [sortBy, setSortBy] = useState('priority');
const [searchText, setSearchText] = useState('');
// 过滤和排序任务
const filteredTasks = useMemo(() => {
let result = tasks.filter(task => {
// 状态过滤
if (filterStatus !== 'all' && task.status !== filterStatus) {
return false;
}
// 搜索过滤
if (searchText && !task.title.toLowerCase().includes(searchText.toLowerCase())) {
return false;
}
return true;
});
// 排序
result.sort((a, b) => {
if (sortBy === 'priority') {
const priorityOrder = { high: 3, medium: 2, low: 1 };
return priorityOrder[b.priority] - priorityOrder[a.priority];
}
if (sortBy === 'title') {
return a.title.localeCompare(b.title);
}
if (sortBy === 'assignee') {
return a.assignee.localeCompare(b.assignee);
}
return 0;
});
return result;
}, [tasks, filterStatus, sortBy, searchText]);
// 添加新任务
const addTask = useCallback((newTask) => {
setTasks([...tasks, { id: Date.now(), ...newTask }]);
}, [tasks]);
// 更新任务状态
const updateTaskStatus = useCallback((taskId, newStatus) => {
setTasks(tasks.map(task =>
task.id === taskId ? { ...task, status: newStatus } : task
));
}, [tasks]);
// 删除任务
const deleteTask = useCallback((taskId) => {
setTasks(tasks.filter(task => task.id !== taskId));
}, [tasks]);
// 统计数据
const stats = useMemo(() => {
return {
total: tasks.length,
todo: tasks.filter(t => t.status === 'todo').length,
inProgress: tasks.filter(t => t.status === 'in-progress').length,
done: tasks.filter(t => t.status === 'done').length,
highPriority: tasks.filter(t => t.priority === 'high').length
};
}, [tasks]);
return (
<div className="task-management-panel">
<h2>任务管理面板</h2>
{/* 控制面板 */}
<div className="control-panel">
<div className="search-box">
<input
type="text"
placeholder="搜索任务..."
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>
</div>
<div className="filter-controls">
<select
value={filterStatus}
onChange={(e) => setFilterStatus(e.target.value)}
>
<option value="all">所有状态</option>
<option value="todo">待办</option>
<option value="in-progress">进行中</option>
<option value="done">已完成</option>
</select>
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
>
<option value="priority">按优先级排序</option>
<option value="title">按标题排序</option>
<option value="assignee">按负责人排序</option>
</select>
</div>
</div>
{/* 统计信息 */}
<div className="stats-panel">
<div className="stat-item total">
<h4>总计</h4>
<p>{stats.total}</p>
</div>
<div className="stat-item todo">
<h4>待办</h4>
<p>{stats.todo}</p>
</div>
<div className="stat-item in-progress">
<h4>进行中</h4>
<p>{stats.inProgress}</p>
</div>
<div className="stat-item done">
<h4>已完成</h4>
<p>{stats.done}</p>
</div>
</div>
{/* 任务列表 */}
<div className="task-list-container">
{filteredTasks.length === 0 ? (
<div className="empty-state">
<i className="fas fa-tasks fa-3x"></i>
<h3>没有任务</h3>
<p>尝试更改筛选条件或添加新任务</p>
</div>
) : (
<ul className="task-list">
{filteredTasks.map(task => (
<TaskItem
key={task.id}
task={task}
onUpdateStatus={updateTaskStatus}
onDelete={deleteTask}
/>
))}
</ul>
)}
</div>
{/* 添加任务表单 */}
<AddTaskForm onAddTask={addTask} />
</div>
);
}
// 任务项组件
const TaskItem = React.memo(function TaskItem({ task, onUpdateStatus, onDelete }) {
const statusColors = {
todo: '#ff6b6b',
'in-progress': '#4ecdc4',
done: '#45b7d1'
};
const priorityColors = {
high: '#ff6b6b',
medium: '#feca57',
low: '#48dbfb'
};
return (
<li className="task-item">
<div className="task-main">
<div className="task-header">
<h4 className="task-title">{task.title}</h4>
<div className="task-actions">
<select
value={task.status}
onChange={(e) => onUpdateStatus(task.id, e.target.value)}
style={color: statusColors[task.status]}
>
<option value="todo">待办</option>
<option value="in-progress">进行中</option>
<option value="done">已完成</option>
</select>
<button
onClick={() => onDelete(task.id)}
className="delete-btn"
>
删除
</button>
</div>
</div>
<div className="task-meta">
<span
className="priority-badge"
style={backgroundColor: priorityColors[task.priority]}
>
{task.priority === 'high' ? '高' :
task.priority === 'medium' ? '中' : '低'}优先级
</span>
<span className="assignee">负责人: {task.assignee}</span>
</div>
</div>
<div
className="status-indicator"
style={backgroundColor: statusColors[task.status]}
></div>
</li>
);
});
// 添加任务表单组件
function AddTaskForm({ onAddTask }) {
const [newTask, setNewTask] = useState({
title: '',
priority: 'medium',
assignee: ''
});
const handleSubmit = (e) => {
e.preventDefault();
if (!newTask.title.trim()) return;
onAddTask({
...newTask,
status: 'todo'
});
setNewTask({
title: '',
priority: 'medium',
assignee: ''
});
};
return (
<form onSubmit={handleSubmit} className="add-task-form">
<h4>添加新任务</h4>
<div className="form-fields">
<input
type="text"
placeholder="任务标题"
value={newTask.title}
onChange={(e) => setNewTask({...newTask, title: e.target.value})}
required
/>
<select
value={newTask.priority}
onChange={(e) => setNewTask({...newTask, priority: e.target.value})}
>
<option value="low">低优先级</option>
<option value="medium">中优先级</option>
<option value="high">高优先级</option>
</select>
<input
type="text"
placeholder="负责人"
value={newTask.assignee}
onChange={(e) => setNewTask({...newTask, assignee: e.target.value})}
/>
<button type="submit">添加任务</button>
</div>
</form>
);
}
map()函数渲染列表,为每个列表项返回React元素React.memo、useMemo、useCallback优化列表性能