获取异步数据
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const ExampleComponent = () => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get('https://api.example.com/data');
setData(response.data);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
return (
<div>
{loading && <p>Loading...</p>}
{error && <p>Error: {error.message}</p>}
{data && <p>Data: {data}</p>}
</div>
);
};
export default ExampleComponent;
不同依赖数组的区别
依赖项不传值
依赖项不传值,每次 render 后会执行 effect 。
通常我不用做不传值的操作,因为不需要 effect 被执行的很频繁。虽然很少,但是也有,我们在用到 useKeepAliveEffect 做页面数据缓存的时候,每次打开页面的时候,都会初始化一下缓存状态,因为我们的项目,不同的入口进入是否需要进行页面缓存的需要不同,有的入口进入页面需要清除缓存。
useEffect(() => {
// 设置initValue的值true,表示组件还未被缓存
ref.current.initValue = true;
});
依赖项传空值
依赖项传空值时,仅第一次 render 后执行 effect 。
这种情况经常被用来在页面打开时请求页面数据,比如详情页等。
useEffect(() => {
// 初始化页面数据的Htttp请求
getData();
}, []);
依赖项传非空值
当依赖项的值非空的情况下,第一次以及依赖项的值发生变化后都会执行 effect 。
我日常开发中的常见场景是弹窗类组件间的通信,每次控制弹窗展示的布尔值发生变化时,都有重新请求数据进行内容渲染。
useEffect(() => {
if (visible) {
// 初始化数据的Htttp请求
getData();
}
}, [visible]);
清除 effect
第四重体验是callback给到的,也是上面简介中提到的,useEffect 是一个合并 API,同时具有与 componentWillUnmount 相同的用途。用来清除 effect 。
前面提到过有些副作用是需要清除的,避免内存泄露。而 useEffect 提供的清除 effect 方法也很简单,返回了一个清除函数,在函数中清除 effect 即可
useEffect(() => {
// 滚动监听
window.addEventListener('scroll', scrollHandle, false);
return () => {
// 清除滚动
window.removeEventListener('scroll', scrollHandle, false);
};
}, []);
函数作为依赖项
在useEffect
的依赖项中使用函数作为依赖项是不推荐的,因为这可能导致不稳定的行为。useEffect
的依赖项应该是稳定的引用,通常是基本类型或引用类型(对象或数组),以确保依赖项的一致性和可预测性。
- 性能问题: 函数每次渲染都会创建一个新的引用,这将导致
useEffect
在每次渲染时都被触发,而不仅仅是在函数的实际变化时。这可能会对性能产生不必要的影响。 - 无限循环: 如果函数在每次渲染时返回一个新的引用,并且该函数在
useEffect
内部被调用,可能导致useEffect
陷入无限循环。
import React, { useEffect, useState } from 'react';
const ExampleComponent = () => {
const [count, setCount] = useState(0);
// 不推荐的例子:函数作为依赖项
useEffect(() => {
const fetchData = async () => {
// 异步操作
};
fetchData();
}, [() => count]); // 不稳定的依赖项,可能导致不稳定的行为
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
export default ExampleComponent;
在上述例子中,[() => count]
这样的依赖项将导致useEffect
在每次渲染时都被触发,而不仅仅是在count
实际变化时触发。
为了避免这类问题,应该将简单的值,如基本类型或引用类型,作为useEffect
的依赖项。如果需要在useEffect
中使用函数,可以考虑使用useCallback
或将该函数声明为组件外的常量,以确保它的引用在渲染之间保持一致。
useLayoutEffect
useLayoutEffect
可能会影响性能。尽可能使用 useEffect
。
useLayoutEffect
是 useEffect
的一个版本,在浏览器重新绘制屏幕之前触发。
useLayoutEffect(setup, dependencies?)
调用 useLayoutEffect
在浏览器重新绘制屏幕之前进行布局测量:
import { useState, useRef, useLayoutEffect } from 'react';
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0);
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height);
}, []);
// ...
用法
在浏览器重新绘制屏幕前计算布局
大多数组件不需要依靠它们在屏幕上的位置和大小来决定渲染什么。他们只返回一些 JSX,然后浏览器计算他们的 布局(位置和大小)并重新绘制屏幕。
有时候,这还不够。想象一下悬停时出现在某个元素旁边的 tooltip。如果有足够的空间,tooltip 应该出现在元素的上方,但是如果不合适,它应该出现在下面。为了让 tooltip 渲染在最终正确的位置,你需要知道它的高度(即它是否适合放在顶部)。
要做到这一点,你需要分两步渲染:
- 将 tooltip 渲染到任何地方(即使位置不对)。
- 测量它的高度并决定放置 tooltip 的位置。
- 把 tooltip 渲染放在正确的位置。
所有这些都需要在浏览器重新绘制屏幕之前完成。你不希望用户看到 tooltip 在移动。调用 useLayoutEffect
在浏览器重新绘制屏幕之前执行布局测量:
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); // 你还不知道真正的高度
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // 现在重新渲染,你知道了真实的高度
}, []);
// ... 在下方的渲染逻辑中使用 tooltipHeight ...
}