React中双.map渲染数据致页面卡顿,寻求性能优化方案
优化双列表渲染的性能问题
核心问题分析
你的代码目前存在几个关键问题,会随着数据量增长加剧性能损耗:
useEffect未设置依赖数组,导致每次组件渲染都会重复发起两次API请求,不仅浪费资源,还会触发不必要的状态更新和重渲染- 列表渲染时未提供唯一
key,React无法高效识别列表项的变化,会导致全量重渲染 deleteRow逻辑错误:依赖全局data状态而非当前行的test值,点击删除按钮时可能删除错误数据- 内联渲染列表项,没有利用React的组件缓存机制,每次父组件重渲染都会重新创建所有列表元素
优化方案与代码实现
1. 修复无限API请求
给useEffect添加空依赖数组,确保仅在组件挂载时请求一次数据,同时用Promise.all并行请求提升加载效率:
useEffect(() => { Promise.all([ axios.get('/users/get'), axios.get('/products/get/') ]) .then(([userRes, productRes]) => { setUserRows(userRes.data); setProductRows(productRes.data); }) .catch(err => console.log(err)); }, []); // 空依赖数组:仅挂载时执行
2. 添加列表项唯一Key
React需要key来追踪列表元素的身份,避免不必要的DOM操作(建议用数据库主键作为key,如果row.test/row.name不唯一的话):
{userRows.map((row) => ( <UserRow key={row.test} row={row} onDelete={deleteRow} /> ))} {productRows.map((row) => ( <ProductRow key={row.name} row={row} /> ))}
3. 提取独立子组件并使用React.memo缓存
将列表项渲染逻辑提取为独立组件,并用React.memo包裹,只有当props变化时才会重渲染:
// 缓存用户行组件 const UserRow = React.memo(({ row, onDelete }) => ( <div> <p>{row.test}</p> <button onClick={() => onDelete(row.test)}>Delete</button> </div> )); // 缓存产品行组件 const ProductRow = React.memo(({ row }) => ( <div> <p>{row.name}</p> {/* 渲染产品图片 */} {/* {row.imageSrc && <img src={row.imageSrc} alt={row.name} />} */} </div> ));
4. 修正deleteRow逻辑并更新本地状态
删除成功后直接更新本地userRows,避免重新请求数据,同时修正参数传递逻辑:
const deleteRow = (testValue) => { axios.delete(`/users/delete/${testValue}`) .then(() => { // 删除成功后过滤本地列表 setUserRows(prevRows => prevRows.filter(row => row.test !== testValue)); }) .catch(err => console.log(err)); };
5. 优化insertRow逻辑(可选)
插入成功后更新本地状态并清空输入框,提升用户体验:
const insertRow = () => { if (!data.trim()) return; // 空值不提交 axios.post('/users/insert', { row: data }) .then(res => { setUserRows(prev => [...prev, res.data]); setData(''); }) .catch(err => console.log(err)); };
完整优化后代码
import React, { useState, useEffect, memo } from 'react'; import axios from 'axios'; import Helmet from 'react-helmet'; // 缓存用户行组件 const UserRow = memo(({ row, onDelete }) => ( <div> <p>{row.test}</p> <button onClick={() => onDelete(row.test)}>Delete</button> </div> )); // 缓存产品行组件 const ProductRow = memo(({ row }) => ( <div> <p>{row.name}</p> {/* 渲染产品图片 */} {/* {row.imageSrc && <img src={row.imageSrc} alt={row.name} />} */} </div> )); export default function Database() { const [userRows, setUserRows] = useState([]); const [productRows, setProductRows] = useState([]); const [data, setData] = useState(''); useEffect(() => { Promise.all([ axios.get('/users/get'), axios.get('/products/get/') ]) .then(([userRes, productRes]) => { setUserRows(userRes.data); setProductRows(productRes.data); }) .catch(err => console.log(err)); }, []); const insertRow = () => { if (!data.trim()) return; axios.post('/users/insert', { row: data }) .then(res => { setUserRows(prev => [...prev, res.data]); setData(''); }) .catch(err => console.log(err)); }; const deleteRow = (testValue) => { axios.delete(`/users/delete/${testValue}`) .then(() => { setUserRows(prev => prev.filter(row => row.test !== testValue)); }) .catch(err => console.log(err)); }; return ( <> <Helmet> <title>Title | Database</title> </Helmet> <input className="mt-60" type="text" value={data} onChange={(e) => setData(e.target.value)} /> <button onClick={insertRow}>Submit</button> {userRows.map((row) => ( <UserRow key={row.test} row={row} onDelete={deleteRow} /> ))} {productRows.map((row) => ( <ProductRow key={row.name} row={row} /> ))} </> ); };
额外性能建议
- 如果数据量极大(数千条以上),考虑实现虚拟滚动,只渲染可视区域内的列表项
- 对API请求添加缓存策略,避免重复请求相同数据
- 确保后端接口返回的数据结构精简,只返回前端需要的字段,减少数据传输量
内容的提问来源于stack exchange,提问作者Josh Haywood
相关产品推荐
相关产品推荐

