为什么你在函数组件里写数据请求,页面一启动就疯狂循环?在React走向函数式组件的时代,副作用管理成了一个绕不开的话题。早在类组件时代,开发者只能借助componentDidMount、componentDidUpdate和componentWillUnmount这几个生命周期方法来处理,代码分散、逻辑割裂。如今,一个统一的钩子函数useEffect,把数据获取、事件订阅、手动DOM操作等“副作用”收敛到一处,彻底改变了函数组件的编写方式。
副作用是什么?简单说就是与组件渲染输出无关的操作。比如从远程API拉取数据、监听窗口大小变化、直接修改文档标题,这些都不直接参与界面产出,却是几乎所有现代Web应用必备的能力。在函数组件诞生初期,这些操作没法做,因为函数执行完就结束了,状态和副作用无处安放。useEffect正是React团队为弥补这一空缺给出的答案。
useEffect的基本结构非常简洁:接受一个副作用函数和一个可选的依赖数组。副作用函数在每次渲染后触发,如果返回一个清理函数,React会在下一次执行副作用前、或者组件卸载前调用它。依赖数组则精细控制执行的时机——只当数组中的值发生变化,副作用才会重新运行。这种设计巧妙地将原本散落在不同生命周期里的增、改、清逻辑,全部集中到了一起。
来看一个典型场景:一个计数器组件,希望在每次点击按钮后,把最新的计数值显示为网页标题。过去在类组件里,需要在componentDidMount里设置初始标题,再在componentDidUpdate里依据前后状态手动判断要不要更新。而在函数组件里,useEffect加一个依赖项就全搞定了。例如一个HookCounterOne组件,内部用useState维护count状态,同时编写useEffect:
useEffect(() => { document.title = `You clicked ${count} times`; }, [count]);
依赖数组里放入count,意味着React会自动盯着count的变化。每当按钮触发setCount,render生成新页面,React比对新旧依赖值,发现count变了,就执行副作用函数,标题也随之刷新。如果依赖数组留空,只会运行一次,等价于componentDidMount;完全不传依赖数组,则每次更新都触发,容易导致性能问题。这里的依赖数组能力,让开发者不用再写一堆条件判断,副作用什么时候生、什么时候灭,一目了然。
清理机制同样内嵌在副作用函数里。如果你在useEffect里订阅了窗口事件,或者创建了一个定时器,返回一个清理函数就能在组件卸载或下一次副作用执行前及时断开。这样可以避免内存泄漏和状态更新到已卸载组件的经典Bug。虽然前面计数器的例子没用到清理,但useEffect预留了这个出口,保障所有副作用的生命周期都是可预期的。
从类组件迁移到函数组件,最大的心理门槛就是丢掉生命周期的概念,改用“副作用与依赖”的心智模型。useEffect相当于三位一体:原本componentDidMount中的初始化代码、componentDidUpdate中的更新判断、componentWillUnmount里的清理逻辑,现在统一写进一个钩子里。对新手来说,一开始可能不习惯,但一旦掌握,会发现组件逻辑不再分散,关联的代码天然聚合,维护成本大幅下降。
基于原文中的示例HookCounterOne,配合App组件即可看到完整效果。整个组件树依旧保持函数式的简洁,没有this绑定、没有render方法,状态用useState,副作用用useEffect,React正以这样的方式让开发者更容易编写可预测的交互界面。随着React官方持续推广Hooks,useEffect已成为函数组件标配,取代类生命周期的进程已经不可逆转。
热门跟贴