React useEffect 相关问题
React useEffect 相关问题
根据 useEffect
官方文档 的介绍,useEffect
会在每次渲染后执行,包括组件首次渲染完成后,相当于 class 组件下 componentDidMount
, componentDidUpdate
及 componentWillUnmount
三个生命周期的组合。
主要用于需要产生副作用的场景,比如数据请求,设置事件监听,DOM 操作,一切产生 效果/Effect 的操作。
只在组件初始化后执行一次
像事件绑定,只需要在组件初始化完成后执行一次即可。这种场景,就相当于 class 组件下的 componentDidMount
,hooks 下的 useEffect
可以通过它的第二个参数来实现。
useEffect(() => {
window.addEventListener("resize", handler);
}, []);
第二个参数接收一个数组,数组里面指定 useEffect
里依赖了哪些变量。只有任意变量的值发生变化时才会执行。
但是如果传递的是空数组,表示它不依赖任何外部变量,所以只会在组件首次渲染完成后执行一次。但务必保证 useEffect
中确实没有使用到组件中的变量,否则会出 bug。因为你对 React 撒了谎,埋下了隐患。
默认情况下,没有传递这个第二参数时,则不会有节流效果,只要组件重新渲染,便会执行。
跳过首次执行
有些情况下是需要排除掉第一次的,比如一个列表,首屏幕数据来自服务器渲染,下拉后再继续请求第二页的数据。
function List({ data }) {
const [listData, setListData] = useState(data);
useEffect(() => {
loadNextPage();
});
}
这种情况没有特别 native 的做法,需要借助 useRef
间接实现。
useRef
并不是之前认识的那个只用来引用 DOM 的 ref,hook 方式中它是可存储任意值的。
function List({ data }) {
const isMount = useRef(false);
const [listData, setListData] = useState(data);
useEffect(() => {
if (!isMount.current) {
isMount.current = true;
return;
}
loadNextPage();
});
}
事件处理器中获取 state
考虑如下代码:
const [num, setNum] = useState(0);
function handler() {
console.log(num);
}
useEffect(() => {
window.addEventListener("resize", handler);
return () => {
window.removeEventListener("resize", handler);
};
}, []);
页面上有两个按钮,点击后分别对 num 进行增减。 同时根据前面的分析,通过传递空数组让事件绑定只执行一次。在窗体 resize 时输出当前的 num。
实测后发现,改变 num 后拖动窗体触发回调,其输出始终为原始值 0 而不会获取到 num 的最新值。
因为事件绑定时,处理器中得到的是 num 的初始状态即值为 0 的时候,此后的执行都是获取到的当时的执行上下文中的这个 num 变量。
解决的办法有两种,有人提了相关 issue,根据 Dan 的回复,大可把这里绑定事件的方式改一下,去掉空数组,即每次渲染时重新绑定事件处理器,这肯定能解决问题,因为重新绑定的时候,获取到的的确是最新的 state。考虑到事件绑定与销毁的开销并不大,同时 useEffect
是不阻塞 UI 渲染的,所以大可不必担心这样的做法性能会很差。
或者,将 num 写进数组,表示这个 useEffect
里的代码会依赖于 num 这个变量,同样能达到效果。因为只要 num 一变,事件会重新绑定。
还有就是前面提到过的 useRef
。将最新的值存入 ref 中,这样事件处理器中直接获取 ref 里的值即可。
const [num, setNum] = useState(0);
const numRef = useRef();
useEffect(() => {
numRef.current = num;
});
function handler() {
console.log(numRef.current);
}
useEffect(() => {
window.addEventListener("resize", handler);
}, []);