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

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

相关推荐
蓝帆傲亦22 分钟前
前端性能极速优化完全指南:从加载秒开体验到丝滑交互
前端·交互
鱼毓屿御35 分钟前
如何给用户添加权限
前端·javascript·vue.js
JustHappy38 分钟前
「web extensions🛠️」有关浏览器扩展,开发前你需要知道一些......
前端·javascript·开源
何中应1 小时前
nvm安装使用
前端·node.js·开发工具
xixixin_1 小时前
【JavaScript 】从 || 到??:JavaScript 空值处理的最佳实践升级
开发语言·javascript·ecmascript
全栈探索者1 小时前
列表渲染不用 map,用 ForEach!—— React 开发者的鸿蒙入门指南(第 4 期)
react.js·harmonyos·arkts·foreach·列表渲染
雯0609~1 小时前
hiprint:实现项目部署与打印3-vue版本-独立出模板设计与模板打印页面
前端·vue.js·arcgis
杜子不疼.1 小时前
【Linux】教你在 Linux 上搭建 Web 服务器,步骤清晰无门槛
linux·服务器·前端
belldeep1 小时前
python:用 Flask 3 , mistune 2 和 mermaid.min.js 10.9 来实现 Markdown 中 mermaid 图表的渲染
javascript·python·flask
凉辰1 小时前
使用uni.createInnerAudioContext()播放指定音频(踩坑分享功能)
开发语言·javascript·音视频