✍️ 作者:我是一名前端小菜鸟,正在努力升级打怪中
前言
大家好,我是一名前端小菜鸟,平时和 console.log
相依为命,经常被报错支配。这篇文章想和同样在学习 React 的朋友分享一下我对 useState
和 useEffect
的理解和使用方式。它们是函数式组件里最核心的两个 Hook,掌握了它们,写 React 真的会轻松很多。
今天咱们聊聊 React 的两大亲儿子 Hook:useState
和 useEffect
。
如果说 React
是一艘游轮,那 Hooks
就是发动机和方向盘,别小看它俩,真整明白了,能让你写出优雅的函数式组件,不仅优雅,还能装X!
useState
:状态的开关
咱先看 useState
,这玩意儿到底是啥?
一句话:useState
就是个函数,让你在函数式组件里拥有自己的"记忆"。
在 JS 里,函数是一等公民:
- 可以当变量传递。
- 可以 return 另一个函数。
- 可以当作组件,return 出
JSX
。
上面这三点如若详细展开又可以写一篇文章了,我这里便以一个表格结束
特性 | 含义 | React 场景 |
---|---|---|
函数可以当变量传递 | 函数是值,可赋值、可传参 | 事件处理、父子通信 |
函数可以返回函数 | 返回新函数,形成闭包 | 自定义 Hook、生成事件处理 |
函数可以作为组件返回 JSX | 函数式组件就是返回 JSX | 函数组件结合 Hooks |
所以当 React 团队给了函数式组件 useState
之后,类组件那一套冗长的 this.state
和 this.setState
就可以拜拜了。
看
javascript
jsx
import { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
return (
<div>
<h1>我点了 {count} 次</h1>
<button onClick={() => setCount(count + 1)}>点我+1</button>
</div>
);
}
是不是很丝滑?
useState
返回一个数组:
- 第一个是当前状态值
- 第二个是你用来更新它的函数
真·函数式,优雅到犯规。
如果更新需要依赖旧的状态则用useState的函数式更新
jsx
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const double = () => {
setCount(prev => prev * 2);
};
return (
<div>
<p>当前:{count}</p>
<button onClick={() => setCount(prev => prev + 1)}>+1</button>
<button onClick={double}>×2</button>
</div>
);
}
setCount(prev=>...),保证每次更新都在最新状态,避免异步更新带来的状态不同步的问题
useEffect
:副作用管理大师
有了 useState
,你能在组件里存状态,那副作用呢?(比如请求接口、订阅事件、操作 DOM...)
这时候就轮到 useEffect
上场了!
什么是副作用?
简单说就是:除了渲染 UI,还要干别的,这就是副作用(side-effect)。
比如:
- 请求接口
- 监听滚动
- 操作
document.title
- 设置定时器
这些都不是纯渲染逻辑,需要在"对的时机"执行。
useEffect和生命周期的那些事
用 useEffect
,你就相当于在写函数式组件里的生命周期函数。 什么是生命周期? 其实它包含三个阶段,如下所示
- 挂载(Mount): 组件第一次创建办并显示在页面上
- 更新(Updata):当组件状态或者属性发生改变时,会重新渲染页面
- 卸载(UnMount):当组件被移除时要清理定时器等,防止内存泄漏
- 挂载后 执行(
componentDidMount
):useEffect(() => {}, [])
- 更新后 执行(
componentDidUpdate
):useEffect(() => {}, [依赖])
- 卸载前 清理(
componentWillUnmount
):useEffect
的返回函数
举个栗子:
javascript
jsx
import { useEffect } from 'react';
function App() {
useEffect(() => {
console.log('组件挂载了!');
return () => {
console.log('组件卸载了!');
};
}, []);
return <h1>Hello Hooks</h1>;
}
useEffect
做了三件事:
- 第一次渲染执行逻辑(
console.log
) - 如果依赖项没变化,不会重复执行
- 卸载时会执行
return
里的清理函数
这就是 React 的"自带扫地僧",帮你回收定时器、取消订阅、终止网络请求,防止内存泄漏。
什么时候请求接口最合适?
很多人问:请求接口放哪儿?
答案是:放 useEffect
里,依赖项写 []
!
这样:
- 组件第一次挂载就执行
- 不会和渲染抢时间(React 会等 DOM 先渲染完)
- 状态更新时不会重复请求
示例:
ini
jsx
复制编辑
import { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('https://api.example.com/users')
.then(res => res.json())
.then(data => setUsers(data));
}, []);
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
依赖项 []
代表只在挂载时请求一次,状态变了也不重复跑。
🧙♂ useEffect
为啥不能直接 async
?
有朋友写:
scss
jsx
复制编辑
useEffect(async () => {
// ...
}, []);
报错了!为啥?
其实是因为useEffect
的回调必须返回一个函数或者啥也不返回,而 async
函数默认返回 Promise
,这不符合要求。
咋办?套一层!
scss
jsx
复制编辑
useEffect(() => {
async function fetchData() {
const res = await fetch('...');
// ...
}
fetchData();
}, []);
✨ 要点:
- 外层是普通函数
- 里头可以是
async
- 需要清理的逻辑还是写在
return
里
卸载时的善后工作
别忘了:定时器、订阅、请求都要在组件卸载时手动清理。
javascript
jsx
复制编辑
useEffect(() => {
const timer = setInterval(() => {
console.log('tick');
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
用clearInterval()函数进行清理,防止内存泄漏
如果不清理?页面都没了,内存还在跑,等着爆炸吗?🤯
总结
useState
:状态管理大师
useEffect
:副作用掌控者 + 生命周期模拟器
两把神兵在手,函数式组件天下我有。
最后的小建议
- 先想清楚"这段逻辑要不要每次都跑" ➡️ 合理写依赖项。
- 不要乱写
async
➡️ 套个内部函数。 - 做好善后 ➡️ 定时器/订阅/请求该清理就清理。
如果这篇对你有帮助,点个 赞 再走吧!
有问题评论区见,我和 console.log
等你来约会