如何避免React列表Map变更时触发不必要的函数调用?
问题描述
在React中使用map渲染用户列表,展示用户名、头像及其他详情,同时提供删除用户按钮。头像地址需通过独立函数getImageUrl生成(而非直接取自JSON),但每次调用删除用户的removeThis函数时,getImageUrl会对所有列表项重复执行。尝试过memo但无效,不想用直接操作DOM的方式解决。
代码示例:
const List = (props) => { const [person, setPerson] = useState(people); /* remove person */ const removeThis = (thisPerson) => { setPerson(person.filter((tp) => tp != thisPerson)); }; /* get image */ function getImageUrl(person) { console.log(person.id, "this should not run on every loop"); return "https://i.imgur.com/" + person.imageId + "s.jpg"; } /* list map */ const listItems = person.map((person) => ( <li key={person.id}> <img src={getImageUrl(person)} alt={person.name} /> <p> <b>{person.name}</b> {" " + person.profession + " "} known for {person.accomplishment} </p> <button onClick={() => removeThis(person)}>Delete</button> </li> )); /* result */ return ( <> <ul>{listItems}</ul> </> ); };
解决方案
方法1:提前预计算图片URL(推荐)
在组件初始化或数据源更新时,提前给每个用户计算好图片URL,避免渲染时重复调用函数。用useMemo缓存处理后的用户列表,仅当数据源变化时重新计算:
const List = (props) => { const [peopleList, setPeopleList] = useState(people); // 预计算带图片URL的用户列表 const peopleWithImageUrl = useMemo(() => { return peopleList.map(person => ({ ...person, imageUrl: `https://i.imgur.com/${person.imageId}s.jpg` })); }, [peopleList]); const removeThis = (thisPerson) => { setPeopleList(peopleList.filter(tp => tp.id !== thisPerson.id)); }; const listItems = peopleWithImageUrl.map((person) => ( <li key={person.id}> <img src={person.imageUrl} alt={person.name} /> <p> <b>{person.name}</b> {" " + person.profession + " "} known for {person.accomplishment} </p> <button onClick={() => removeThis(person)}>Delete</button> </li> )); return ( <> <ul>{listItems}</ul> </> ); };
方法2:封装列表项组件并配合memo+useCallback
之前memo无效的核心原因是删除函数每次渲染都会重新创建,导致子组件误判props变化。用useCallback缓存删除函数,再给列表项组件加memo:
// 独立的列表项组件,用memo缓存 const PersonItem = memo(({ person, onDelete }) => { // 仅当person变化时计算图片URL const imageUrl = useMemo(() => { return `https://i.imgur.com/${person.imageId}s.jpg`; }, [person]); return ( <li> <img src={imageUrl} alt={person.name} /> <p> <b>{person.name}</b> {" " + person.profession + " "} known for {person.accomplishment} </p> <button onClick={() => onDelete(person)}>Delete</button> </li> ); }); const List = (props) => { const [peopleList, setPeopleList] = useState(people); // 用useCallback缓存删除函数,避免重复创建 const removeThis = useCallback((thisPerson) => { setPeopleList(prev => prev.filter(tp => tp.id !== thisPerson.id)); }, []); return ( <> <ul> {peopleList.map(person => ( <PersonItem key={person.id} person={person} onDelete={removeThis} /> ))} </ul> </> ); };
方法3:直接在渲染时拼接URL
如果getImageUrl逻辑简单,直接在map里拼接URL,省去函数调用的额外开销:
const listItems = person.map((person) => ( <li key={person.id}> <img src={`https://i.imgur.com/${person.imageId}s.jpg`} alt={person.name} /> <p> <b>{person.name}</b> {" " + person.profession + " "} known for {person.accomplishment} </p> <button onClick={() => removeThis(person)}>Delete</button> </li> ));
内容的提问来源于stack exchange,提问作者Neo
相关产品推荐
相关产品推荐

