React Hooks 初识:从 useState 到 useEffect
在现代前端开发中,React 函数式组件凭借其简洁、可读性强以及逻辑复用便利等优势,逐渐成为主流。而让函数式组件具备状态管理和副作用处理能力的关键,正是 React Hooks。本文将带你从最基础的 useState 开始,逐步理解 useEffect 的必要性与使用方式。
1. “记忆”从何而来?—— useState 如何让函数组件拥有状态
在类组件时代,我们通过 this.state 来管理组件内部的状态。而在函数式组件中,useState Hook 提供了同样的能力。
思考下面一段代码:
import { useState } from 'react';
export default function App(){
const [num, setNum] = useState(1);
return (
<div onClick ={()=>setNum(num+1)}>
)
}
这是最基础的usestate的用法:
我来简单解释一下:useState()接收一个初始值,我们这里设置为1,同时,它返回一个包含两个元素的数组,第一个是当前状态的值(num),第二个是用于修改状态值的函数(setNum),我们在这里进行了解构操作,命名为num,setNum。
上面这段代码表示的逻辑可以用一张图来完美表示->
值得注意的是:event代指一系列能够引起状态值变化的事件,而不单单是点击事件
- 事件触发引起状态值改变
- 状态值的改变会导致页面的重新渲染(更新)
- 当然了,组件也可以引起一些事件执行,所以形成了三角关系
进阶用法
import { useState } from 'react';
export default function App(){
const [num, setNum] = useState(()=>{
const num1 =1+2;
const num2 =2+3;
return num1+num2;
});
return (
// 修改函数中可以直接传新的值,也可以传入一个函数,这个函数的的参数是上一次的state
<div onClick={()=>setNum((prevNum)=>{console.log(prevNum);return prevNum+1;})}>
{num}
</div>
)
}
这段代码展示了 useState 的两种典型用法:
- 惰性初始化:当初始状态需要通过复杂计算获得时,可以传入一个函数。React 会在组件首次渲染时调用该函数,并将其返回值作为初始状态。
- 函数式更新:在调用
setNum时传入一个函数,该函数接收前一次的状态作为参数。这在状态依赖于前一个状态时非常有用,能避免因闭包导致的状态“滞后”问题。
初始化传入的函数只能是一个纯函数
纯函数是指,当我们给一个函数一个固定的输入那么它一定会得到相同的输出,他是固定的能够确定的
比如:
const add =function(x,y){
fetch // 不确定的
return x+y;
}
如果x,y是需要通过fetch请求获得的,但是我们实际上并不能确定是否能够拿到结果,因为这个请求有可能失败,所以在useState中执行异步任务是不可取的。
useState 本身只负责状态的声明与更新。它无法处理“副作用”——比如数据请求、订阅、定时器等与 UI 渲染无关但又必须执行的操作。
但是实际开发中,我们的数据往往需要通过异步的请求来获取,但是useState做不到这点,这时候useEffect就登场了
二、副作用的引入:为什么需要 useEffect?
什么是副作用?简单来说,任何在组件渲染之外发生的、影响外部系统或依赖外部系统的行为,都属于副作用。
思考下面一段代码:
function add (nums){
nums.push(3);
return nums.reduce((pre,cur)=>pre+cur,0);
}
const nums = [1,2];
add(nums);// 副作用
console.log(nums.length);
它暗示了一个重要概念:如果函数在执行过程中修改了外部变量(如 nums.push(3)),或者依赖了非参数输入(如全局变量、网络请求等),那么它就不再是“纯函数”,也就产生了副作用。
在 React 中,组件理想上应是纯函数:给定 props 和 state,总是返回相同的 JSX。但现实应用中,我们常常需要:
- 组件挂载后发起 API 请求;
- 监听某些状态变化并作出响应;
- 设置/清除定时器或事件监听器。
这些操作不能放在渲染逻辑中(会导致不可预测行为或性能问题),于是 useEffect 应运而生。
三、“行动”何时发生?—— useEffect 的三种典型执行时机
useEffect 是 React 提供的用于处理副作用的 Hook。它接收两个参数:一个副作用函数,以及一个可选的依赖数组。它由React自动调用
也许副作用这个词会让你感到困惑,但它表示的含义是:组件渲染之外、与外部系统交互或产生状态变化的操作(如数据请求、订阅、定时器等),它们不影响 JSX 的纯函数输出,但会对程序状态或环境产生“额外影响”。
在组件挂载时调用
思考下面一段代码:
import { useState,// 响应式状态
useEffect, // 副作用
} from 'react';
async function queryData(){
const data =await new Promise((resolve)=>{
setTimeout(()=>{
resolve(666);
},2000);
})
return data;
}
export default function App(){
const [num, setNum] = useState(0);
useEffect(()=>{
console.log('xxx');
queryData().then((data)=>{
setNum(data);
})
},[])
console.log('yyy');
return (
<div onClick={()=>setNum((prevNum)=>prevNum+1)}>
{num}
</div>
)
}
我们这里使用一个定时器来模拟实际开发中的异步数据请求,但是在非严格模式下,控制台实际上只打印了一次‘xxx’,这里似乎并没有我们想看到的所谓的副作用,原因是什么呢?
实际上是我们传入的依赖项在暗暗发力,当我们传入一个空数组作为useEffect的依赖项时,它的含义就是,当前函数不依赖于任何东西,所以任何状态值的改变都无法触发这个副作用函数,它仅在函数挂载时才被调用一次。
这里还有一个值得注意的地方,我们在函数后想要打印‘yyy’,但是实际上,'yyy'却在'xxx'之前输出,这也是react的小细节,这些生命周期函数,会在组件挂载后执行,不与DOM争抢资源
这种用法通常用于数据的初始化
queryData().then((data)=>{
setNum(data);
})
在依赖项变化时调用
我们替换useEffect为这样一段代码
useEffect(()=>{
console.log('zzz');
},[num])
这次我们设置依赖项为num,当我们通过点击事件触发了num的更新时,就会调用useEffect方法,如果你学习过vue,那么可能会觉得这就像watch或者钩子函数onUpdate。
你现在应该已经了解了在状态更新时调用副作用函数的方式,那现在来考虑这样一段代码
useEffect(()=>{
console.log('effect');
//一直执行的定时器
const timer = setInterval(()=>{
console.log(num);
},1000)
},[num])
我们已经明白,当依赖项更新时,我们就会执行useEffect();那么这段代码就很显而易见了,每当我们更新了num时,就会新建一个一直运行的定时器,这会有一个什么后果呢
当我们点击更新的次数越来越多就会创建N个定时器,这显然不是我们想要的,那么我们要怎么做才能够销毁掉已经创建的定时器呢?
下一次执行前或卸载时调用
useEffect可以定义一个return函数(被称为清理函数),它能够在下一次执行uesEffect之前或者组件被卸载时被调用
return ()=>{
clearInterval(timer);
}
我们在清理函数中进行定时器的销毁,就能确保调用这次的useEffect()之前,上一次的定时器被销毁
组件卸载时调用:
return (
<>
<div onClick={()=>setNum((prevNum)=>prevNum+1)}>
{num}
</div>
{num%2 === 0 &&<Demo/>}
</>
我们这里在num为偶数时加载组件Demo,当组件被卸载时就会调用这个返回的函数 如果我们不对定时器进行回收,就会造成内存泄漏,导致页面崩溃
这对于定时器、事件监听、WebSocket 连接等场景至关重要。
小结
useState赋予函数组件管理内部状态的能力,但它仅适用于同步、确定性的数据更新,无法处理异步操作或外部交互。useEffect则专门用于处理“副作用”——那些发生在渲染之外、与外部系统交互的操作,如数据请求、订阅、定时器等。- 通过依赖数组,我们可以精确控制副作用的执行时机:空数组表示仅在挂载时运行,包含状态则在该状态变化时重新执行。
- 每次
useEffect重新运行前,或组件卸载时,React 会自动调用其返回的清理函数(cleanup function) ,这是避免内存泄漏、确保资源正确释放的关键。
掌握 useState 与 useEffect,就掌握了 React 函数式组件的核心心智模型。在此基础上,你将能更自信地构建复杂、健壮且可维护的现代前端应用。