为什么在render里调setState,代码会和你“翻脸”?

我明明只是修改个状态,怎么页面开始疯狂抽搐甚至直接崩了?

作为前端开发者,相信很多人都曾在React的render函数中尝试过setState,结果却遇到了意想不到的问题。今天小杨就来和大家聊聊这背后的原因,以及如何避免这个常见的陷阱。

一个亲身经历的bug案例

前几天我在开发一个商品列表组件时,遇到了一个奇怪的问题。页面在加载时会不断刷新,最后甚至直接白屏了。经过一番排查,我发现问题出在了下面这段代码:

jsx 复制代码
function ProductList({ products }) {
  const [filteredProducts, setFilteredProducts] = useState(products);
  const [sortOrder, setSortOrder] = useState('asc');
  
  // 这里犯了低级错误!
  if (products.length !== filteredProducts.length) {
    setFilteredProducts(products);
  }
  
  return (
    <div>
      {/* 渲染产品列表 */}
    </div>
  );
}

看起来我只是在props变化时更新state,但这样做却导致了组件的无限重新渲染!

为什么render中不能调用setState?

简单来说,在render过程中调用setState就像是在盖房子时不断修改蓝图------工程永远无法完工,反而可能把工地搞得一团糟。

React的渲染过程分为几个阶段:

  1. Render阶段:计算虚拟DOM的变化
  2. Commit阶段:将变化应用到真实DOM
  3. 清理阶段:执行副作用和生命周期方法

当我们在render函数中调用setState时,React会:

  • 标记需要更新状态
  • 重新执行render函数
  • 发现又调用了setState
  • 再次标记需要更新状态
  • ...形成无限循环

正确的解决方案

对于上面的问题,我最终使用了useEffect来避免在render中直接调用setState:

jsx 复制代码
function ProductList({ products }) {
  const [filteredProducts, setFilteredProducts] = useState(products);
  const [sortOrder, setSortOrder] = useState('asc');
  
  // 使用useEffect在适当的时候更新状态
  useEffect(() => {
    setFilteredProducts(products);
  }, [products]);
  
  const sortedProducts = useMemo(() => {
    return [...filteredProducts].sort((a, b) => {
      return sortOrder === 'asc' ? a.price - b.price : b.price - a.price;
    });
  }, [filteredProducts, sortOrder]);
  
  return (
    <div>
      {/* 渲染产品列表 */}
    </div>
  );
}

其他常见陷阱和解决方案

  1. 事件处理中的setState
    这是setState最安全的调用场所
jsx 复制代码
function Counter() {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    setCount(prevCount => prevCount + 1); // 正确:在事件处理中调用
  };
  
  return <button onClick={handleClick}>Count: {count}</button>;
}
  1. useEffect中的setState
    需要注意依赖项数组,避免无限循环
jsx 复制代码
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(userData => {
      setUser(userData); // 正确:在useEffect中调用
    });
  }, [userId]); // 确保依赖项正确
}

总结一下

  • 🚫 避免在render函数中直接调用setState
  • ✅ 在事件处理程序或useEffect中调用setState
  • 🔄 注意setState可能引起的重新渲染,合理使用useMemo和useCallback优化性能

⭐ 写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

相关推荐
To_OC43 分钟前
LC 51 N 皇后:我以为难的是回溯,结果栽在了对角线下标
javascript·算法·leetcode
不好听6131 小时前
从一行 JSX 到屏幕像素:前端开发者必须懂的浏览器渲染管线
前端
恒拓高科WorkPlus1 小时前
企业级内网即时通讯建设模型:BeeWorks内网IM四层架构
前端
前端小李子2 小时前
前端环境变量裸奔?我用 EnvShield 给它穿了件防弹衣
前端
youtootech2 小时前
HarmonyOS《柚兔学伴》项目实战25-我的页面、Web 嵌入与项目总结
前端·华为·harmonyos
小林ixn2 小时前
从零到一理解 React 父子组件通信:手写一个 Todo 应用带你彻底搞懂单向数据流
前端·javascript·react.js
醇氧4 小时前
CountDownLatch / CyclicBarrier / Semaphore 面试高频问答清单
前端·面试·职场和发展
窝子面5 小时前
手搓最简前后端协作-node
javascript·数据库
qetfw5 小时前
MXU:Tauri 2 + React 的 MaaFramework 跨平台 GUI 源码
前端·python·react.js·前端框架·开源项目·效率工具