在React项目开发中,动态渲染HTML列表是非常常见的需求,比如展示后台返回的商品列表、消息列表等。很多开发者在实现这个功能时,经常会遇到各种意料之外的问题,同时搭配useEffect处理列表相关的副作用时也容易踩坑。下面我们先来看一个基础的动态列表渲染示例,再逐步分析常见陷阱和useEffect的正确用法。

动态渲染HTML列表的常见陷阱
1. 错误使用key属性
很多开发者会用列表的索引index作为key值,这在列表不会增删、排序的场景下暂时没问题,但如果列表有动态修改,就会引发DOM复用错误。比如删除列表第一项时,index会重新分配,React会误以为后面的项被修改了,导致渲染异常。
正确的做法是用列表项的唯一标识作为key,比如后台返回数据的id字段:
// 错误示例:用index作为key
const BadList = ({ list }) => {
return (
<ul>
{list.map((item, index) => (
<li key={index}>{item.name}</li>
))}
</ul>
);
};
// 正确示例:用唯一id作为key
const GoodList = ({ list }) => {
return (
<ul>
{list.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
};2. 直接操作DOM修改列表
React是数据驱动视图的框架,有些开发者习惯用原生JS获取DOM节点后直接修改列表内容,这会破坏React的状态管理逻辑,导致数据和视图不一致。比如通过document.querySelector获取ul元素后手动添加li,后续状态更新时React的虚拟DOM对比会失效。
3. 列表状态更新逻辑错误
更新列表状态时没有遵循不可变数据原则,直接修改原数组再设置状态,会导致React无法检测到状态变化,列表不会重新渲染。比如直接调用list.push(newItem)之后再用setList(list),因为引用没变,React会认为状态没有更新。
正确的更新方式应该是返回新的数组:
const ListComponent = () => {
const [list, setList] = useState([]);
// 错误更新方式
const badAddItem = (newItem) => {
list.push(newItem);
setList(list); // 引用没变,不会触发渲染
};
// 正确更新方式
const goodAddItem = (newItem) => {
setList([...list, newItem]); // 返回新数组,引用变化
};
return (
<ul>
{list.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
};useEffect的正确使用指南
1. 合理设置依赖项
useEffect的第二个参数是依赖项数组,只有依赖项发生变化时,副作用才会重新执行。如果渲染列表时需要请求后台数据,通常会把请求逻辑放在useEffect里,依赖项可以设置为空数组(只在组件挂载时执行一次),或者根据需要的触发条件设置对应的状态依赖。
const ListWithFetch = () => {
const [list, setList] = useState([]);
const [page, setPage] = useState(1);
// 仅挂载时请求一次数据
useEffect(() => {
const fetchData = async () => {
const res = await fetch(`https://ipipp.com/api/list?page=${page}`);
const data = await res.json();
setList(data.list);
};
fetchData();
}, []); // 空依赖数组,只执行一次
// 页码变化时重新请求数据
useEffect(() => {
const fetchData = async () => {
const res = await fetch(`https://ipipp.com/api/list?page=${page}`);
const data = await res.json();
setList(data.list);
};
fetchData();
}, [page]); // 依赖page,page变化时执行
return (
<div>
<ul>
{list.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
<button onClick={() => setPage(page + 1)}>加载更多</button>
</div>
);
};2. 编写清理函数避免内存泄漏
如果useEffect里有订阅、定时器等异步操作,需要在组件卸载或者依赖变化前清理,否则会出现内存泄漏。比如请求数据时组件已经卸载,返回的数据再调用setState就会报错。
useEffect(() => {
let isCanceled = false; // 标记请求是否取消
const fetchData = async () => {
const res = await fetch('https://ipipp.com/api/list');
const data = await res.json();
if (!isCanceled) { // 组件未卸载才更新状态
setList(data.list);
}
};
fetchData();
// 清理函数,组件卸载时执行
return () => {
isCanceled = true;
};
}, []);3. 避免在useEffect里直接修改依赖项导致无限循环
如果在useEffect里修改了依赖项的状态,又没有做条件判断,会导致useEffect反复执行,进入无限循环。比如依赖项是list,又在useEffect里调用setList更新list,就会不断触发副作用。
解决方式是在更新前做判断,只有数据确实变化时才更新状态,或者调整依赖项,避免循环依赖。
总结
动态渲染HTML列表时,要注意key的正确使用、遵循React的数据驱动原则、用不可变方式更新状态;使用useEffect时,要合理设置依赖项、编写清理函数、避免循环触发。掌握这些要点,就能有效规避常见问题,写出更健壮的React组件。