userCallback & userEffect 使用注意事项
随着 v16.8 Hooks 问世后,人们开始大量使用函数组件,当使用函数进行编写组件时,如果需要在内部执行 API 接口的调用,需要用到 useEffect 生命周期钩子。
useEffect 用于处理组件中的 effect,通常用于请求数据,事件处理,订阅等相关操作。
在最初版本的文档指出,防止 useEffect 出现无限循环,需要提供空数组 [] 作为 useEffect 依赖项,将使钩子只能在组件的挂载和卸载阶段运行。因此,我们会看到在很多使用 useEffect 的地方将 [] 作为依赖项传入。
使用 useEffect 出现无限循环的原因是,useEffect 在组件 mount 时执行,但也会在组件更新时执行。因为我们在每次 请求数据之后基本上都会设置本地的状态,所以组件会更新,因此 useEffect 会再次执行,因此出现了无限循环的情况。 然而,这种处理方式就会出现 react-hooks/exhaustive-deps 规则的警告,因此代码中常常会通过注释忽略此警告。
然而,这种处理方式就会出现 react-hooks/exhaustive-deps
规则的警告,因此代码中常常会通过注释忽略此警告。
// eslint-disable-next-line react-hooks/exhaustive-deps
import React, { useState, useEffect } from 'react'
import { fetchUserAction } from '../api/actions.js'
const UserContainer = () => {
const [user, setUser] = useState(null);
const handleUserFetch = async () => {
const result = await fetchUserAction();
setUser(result);
};
useEffect(() => {
handleUserFetch();
// 忽略警告
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!user) return <p>No data available.</p>
return <UserCard data={user} />
};
最初,很多人认为这个警告毫无意义,从而选择进行忽略,而不去试图探索它是如何产生的。
其实,有些人没有意识到,handleUserFetch()
方法在组件每次渲染的时候都会重新创建(组件有多少次更新就会创建多少次)。
关于 react-hooks/exhaustive-deps
详细的讨论,可以看下这个 issue。
useCallback 的作用在于利用 memoize 减少无效的 re-render,来达到性能优化的作用。
这就是为什么我们需要在 useEffect
中调用的方法上使用 useCallback
的原因。通过这种方式,我们可以防止 handleUserFetch()
方法重新创建(除非其依赖项发生变化) ,因此这个方法可以用作 useEffect
钩子的依赖项,而不会导致无限循环。
上边的例子应该这样重写:
import React, { useState, useEffect, useCalllback } from 'react'
import { fetchUserAction } from '../api/actions.js'
const UserContainer = () => {
const [user, setUser] = useState(null);
// 使用 useCallback 包裹
const handleUserFetch = useCalllback(async () => {
const result = await fetchUserAction();
setUser(result);
}, []);
useEffect(() => {
handleUserFetch();
}, [handleUserFetch]); /* 将 handleUserFetch 作为依赖项传入 */
if (!user) return <p>No data available.</p>
return <UserCard data={user} />
};
我们将 handleUserFetch
作为 useEffect
的依赖项,并将它包裹在 useCallback
中。如果此方法使用外部参数,例如 userId
(在实际开发中,可能希望获取特定的用户) ,则此参数可以作为 useCallback
的依赖项传入。只有 userId
发生变化时,依赖它的 handleUserFetch
才重写改变。