You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 12:55:19