使用 useEffect 比較容易出現(xiàn)問題是閉包陷阱,盡量嘗試不使用 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 函數(shù):

image
在 events 中存了4個 handleClick,分別打印 0、1、2、3,分別引用了4個閉包變量 count。在使用 useEffect 時,沒有沒有 deps,那么當前的 handleClick,永遠是第一次渲染 App 時,所創(chuàng)建的 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,創(chuàng)建了新的 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 移到內部后,發(fā)現(xiàn) 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();