有时我们不希望一个useEffect 钩子在初始渲染时运行。这可能有很多原因,但一个常见的用例是当我们正在获取该钩子所依赖的数据时。最初,数据是空的,所以运行该钩子是没有意义的。
一个理论上的例子
在下面这个组件中,我们fetch 一些数据,然后我们打算在取完数据后用这些数据doSomething 。
function MyComponent() {
const [data, setData] = useState();
// An effect to fetch the data
useEffect(() => {
fetch('/api/some-api')
.then((res) => res.json())
.then((d) => {
setData(d);
});
}, []);
// Do something else with the data
useEffect(() => {
doSomething(data);
}, [data]);
}
在初始渲染时,我们fetch 数据,但我们也运行我们的第二个效果。
重要的是: useEffect 钩子将总是在加载时运行,不管它的依赖数组中是否有任何东西。
我们可能不想在我们的data ,当它是undefined (因为它将在初始渲染时)时实际运行这个效果,而是想等到它从API调用中被填充。
useRef来拯救!
解决这个问题的一个办法是采用useRef 钩子来跟踪组件是否已经安装。虽然ref 通常被认为是获得对DOM元素引用的一种方式,但它也可以用来引用其他对象和基元。
在这种情况下,我们将创建一个ref ,指向一个布尔值,以跟踪组件是否已经安装。它一开始会是false ,但一旦effect 第一次运行,我们就可以把它改为true 。
这可能是更容易理解的代码。
function MyComponent() {
const [data, setData] = useState();
const isMounted = useRef(false);
// An effect to fetch the data
useEffect(() => {
fetch('/api/some-api')
.then((res) => res.json())
.then((d) => {
setData(d);
});
}, []);
// Do something else with the data
useEffect(() => {
if (isMounted.current) {
doSomething(data);
} else {
isMounted.current = true;
}
}, [data]);
}
isMounted.current 开始时是 ,所以我们的第二个 钩子的第一次运行不会调用 。相反,它将把 设置为 。在钩子的后续运行中, 将成为 , 将在我们的数据上执行。false useEffect doSomething isMounted.current true isMounted.current true doSomething
为什么采用这种方法?
为这个目的使用ref ,有几个好处。
- 我们可以确信,在钩子的任何后续运行中,我们的
data将被改变--这是依赖数组中的唯一值。 - 通过不使用状态来跟踪
isMounted,我们就不会强迫进行额外的组件渲染。isMounted.current = true正在改变我们的ref(这是它的预期用途),因此它是同一个对象/不会导致重新渲染。
总结
useEffect 钩子可能特别具有挑战性。通过了解useRef 钩子,我们可以对我们的效果运行时间获得更多的控制。