使用 useEffect 比较容易出现问题是闭包陷阱,尽量尝试不使用 useEffect,见第4。
1. 错误演示
import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
import { Button } from "antd";
import "./styles.css";
window.events = [];
window.test = function () {
window.events.forEach(it => {
it()
});
}
function App() {
const [count, setCount] = useState(0);
const ref = useRef();
const handleClick = (event) => {
console.log(count);
}
window.events.push(handleClick);
useEffect(() => {
ref.current.addEventListener("click", handleScroll);
return () => {
ref.current.removeEventListener("click", handleScroll);
};
}, []);
return (
<div ref={ref}>
<div>{count}</div>
<Button
onClick={() => {
setCount(count + 1);
}}
>
点击
</Button>
</div>
);
}
const rootElement = document.getElementById("root");
function render() {
ReactDOM.render(<App />, rootElement);
}
render();
点击3次后,在控制台调用 test 函数:
在 events 中存了4个 handleClick,分别打印 0、1、2、3,分别引用了4个闭包变量 count。在使用 useEffect 时,没有没有 deps,那么当前的 handleClick,永远是第一次渲染 App 时,所创建的 handleClick。
2. 使用 useEffect 时要保证依赖正确
上述问题因为依赖的配置不正确导致的,如果 effect 中有依赖外部变量,需要添加到依赖中
import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
import { Button } from "antd";
import "./styles.css";
function App() {
const [count, setCount] = useState(0);
const ref = useRef();
const handleClick = (event) => {
console.log(count);
}
useEffect(() => {
ref.current.addEventListener("scroll", handleClick);
return () => {
ref.current.removeEventListener("scroll", handleClick);
};
// effect 中依赖了 handleClick,
// 每次 App reRender,创建了新的 handleClick
// 需要添加到 deps 中,否则依赖的 handleClick 所在的作用域,永远是第一次 render,已经过期了
}, [handleClick]);
return (
<div ref={ref} style={{ overflow: 'auto', height: '100vh', border: '1px solid red' }}>
<div>{count}</div>
<Button
onClick={() => {
setCount(count + 1);
}}
>
点击
</Button>
<div style={{ height: 20000}}></div>
</div>
);
}
const rootElement = document.getElementById("root");
function render() {
ReactDOM.render(<App />, rootElement);
}
render();
3. 尝试将 effect 的依赖项,定义到 effect 内部
import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
import { Button } from "antd";
import "./styles.css";
function App() {
const [count, setCount] = useState(0);
const ref = useRef();
useEffect(() => {
// 将 handleClick 移到内部后,发现 effect 内 依赖了 count state
// 将 count 添加到依赖中
// 否则 handleClick 依赖的 count,永远是第一次 App 调用的作用域下的
const handleClick = (event) => {
console.log(count);
}
ref.current.addEventListener("scroll", handleClick);
return () => {
ref.current.removeEventListener("scroll", handleClick);
};
}, [count]);
return (
<div ref={ref} style={{ overflow: 'auto', height: '100vh', border: '1px solid red' }}>
<div>{count}</div>
<Button
onClick={() => {
setCount(count + 1);
}}
>
点击
</Button>
<div style={{ height: 20000}}></div>
</div>
);
}
const rootElement = document.getElementById("root");
function render() {
ReactDOM.render(<App />, rootElement);
}
render();
4. 尽量不使用 useEffect
import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
import { Button } from "antd";
import "./styles.css";
function App() {
const [count, setCount] = useState(0);
const ref = useRef();
const handleScroll = (event) => {
console.log(count);
}
return (
<div ref={ref} onScroll={handleScroll} style={{ overflow: 'auto', height: '100vh', border: '1px solid red' }}>
<div>{count}</div>
<Button
onClick={() => {
setCount(count + 1);
}}
>
点击
</Button>
<div style={{ height: 20000}}></div>
</div>
);
}
const rootElement = document.getElementById("root");
function render() {
ReactDOM.render(<App />, rootElement);
}
render();