为什么在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

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

相关推荐
无羡仙19 分钟前
Vue插槽
前端·vue.js
哈__33 分钟前
React Native 鸿蒙跨平台开发:PixelRatio 像素适配
javascript·react native·react.js
用户6387994773051 小时前
每组件(Per-Component)与集中式(Centralized)i18n
前端·javascript
SsunmdayKT1 小时前
React + Ts eslint配置
前端
开始学java1 小时前
useEffect 空依赖 + 定时器 = 闭包陷阱?count 永远停在 1 的坑我踩透了
前端
zerosrat1 小时前
从零实现 React Native(2): 跨平台支持
前端·react native
狗哥哥1 小时前
🔥 Vue 3 项目深度优化之旅:从 787KB 到极致性能
前端·vue.js
青莲8431 小时前
RecyclerView 完全指南
android·前端·面试
青莲8431 小时前
Android WebView 混合开发完整指南
android·前端·面试
GIS之路1 小时前
GDAL 实现矢量数据转换处理(全)
前端