问题引出
有时候,你的 effect 可能会使用一些频繁变化的值。你可能会忽略依赖列表中 state,但这通常会引起 Bug:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // 这个 effect 依赖于 `count` state
}, 1000);
return () => clearInterval(id);
}, []); // 🔴 Bug: `count` 没有被指定为依赖
return <h1>{count}</h1>;
}
引发的问题:
- 只会在初始化的时候执行一次
知识点:使用Effect Hook,通过跳过Feect进行性能优化
如果想执行并运行一次的effect(仅在组件挂载和卸载时执行),可以传递一个空数组[]作为第二个参数,这就是告诉React你的effect不依赖于props或state中的任何值,所以它永远不会重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。
- count的值永远不会超过1
因为当effect执行时,我们回创建一个闭包,并将count的值保存在该闭包内,且初始值为0,每隔一秒,回调就会执行setCount(0 + 1),所以count 永远不会超过 1。
- Q 如果这个时候增加一个button,来强制更改count的值呢?
在一秒之后count会重新赋值为1,即还是取闭包内的count,进行+1
import React,{ useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // 这个 effect 依赖于 `count` state
}, 1000);
return () => clearInterval(id);
}, []); // 🔴 Bug: `count` 没有被指定为依赖
return (
<div>
<h1>{count}</h1>
<button onClick={()=>{setCount(count+1)}}>+1</button>
</div>
)
}
ReactDOM.render(
<Counter />, document.getElementById('container'),
);
如果这个时候,将[]改为count呢?
还会有其他问题
每次改变时定时器都会被重置。事实上,每个 setInterval 在被清除前(类似于 setTimeout)都会调用一次。但这并不是我们想要的。
知识点:useEffect会在调用一个新的effect之前对前一个effect进行清理,如下面例子
要解决这个问题,可以使用setState的函数式进行更新
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1); // ✅ 在这不依赖于外部的 `count` 变量
}, 1000);
return () => clearInterval(id);
}, []); // ✅ 我们的 effect 不适用组件作用域中的任何变量
return <h1>{count}</h1>;
}
这种写法,可取到Count的实时数值