我正在使用Create-React-App和(优秀的(use-http进行自定义useFetch钩子。目标是在登录到帐户区域时进行多次 API 调用:
const [user, setUser] = useState(null)
const [profile, setProfile] = useState(null)
const [posts, setPosts] = useState(null)
const request = useFetch('/')
const initializeAccount = async () => {
try {
const user = await request.get('api/user/')
const profile = await request.get('api/profile/')
const posts = await request.get('api/posts/')
if (user) {
setUser(user.data)
}
if (profile) {
setProfile(profile.data)
}
if (posts) {
setPosts(posts.data)
}
} catch (e) {
console.log('could not initialize account')
}
}
useEffect(() => {
initializeAccount()
return () => console.log('unmount')
})
我尝试使用[]
作为依赖数组,但我收到一个 linting 错误,说initializeAccount
移动到依赖数组。如果我添加它,该函数将无休止地运行。
设置依赖项数组以便调用此函数一次的正确方法是什么?此外,在这种情况下,处理每个 API 调用中止的正确方法是什么?
我的伙计,为了对 api 调用运行一次 useEffect,你必须这样做:
useEffect(() => {
initializeAccount()
return () => console.log('unmount')
},[])
希望对您有所帮助。
lint 错误是有原因的,忽略它可能会导致很难找到的错误。
无依赖关系数组
如果完全排除依赖数组(useEffect(() => { /* ... */ });
(,效果将在每次渲染上运行,导致无限循环。
空依赖项数组
如果添加一个空的依赖数组 (useEffect(() => { /* ... */ }, [])
(,效果将仅在第一次渲染时运行。这可能在这里有效,但它会导致其他情况下的问题,或者将来发生某些变化。例如,假设request
初始化需要一些时间(所以它是第一个undefined
,稍后更改为对象(,initializeAccount
如下所示。现在initalizeAccount
只会在第一次使用request === undefined
渲染时运行,当request
在稍后阶段准备就绪并且组件被重新渲染时,initalizeAccount
不会再次运行,并且功能将静默失败。
const initializeAccount = async () => {
if (request) {
const user = await request.get('api/user/')
if (user) {
setUser(user.data)
}
}
}
填充的依赖数组(正确的方式(
如果将initializeAccount
添加到依赖数组 (useEffect(() => { /*...*/ }, [initalizeAccount])
(,则每次更改initializeAccount
时都会运行效果。对于对象(和函数(,React 将上次渲染的引用与当前渲染的引用进行比较,以确定对象是否已更改。您定义initializeAccount
的方式将在每次渲染时重新定义,因此引用将是新的,并且效果将在每次渲染上运行,就像您体验的那样。
您需要做的是获得对initalizeAccount
的稳定引用。为此 React 提供了useCallback
,它类似于useEffect
,因为它有一个依赖数组。如果我们用useCallback
定义initializeAccount
,React 将确保渲染之间的引用相同。只有当useCallback
的依赖数组中的一个依赖项发生变化时,才会重新定义函数,进而触发useEffect
再次运行。这样,您可以遵守lint规则,同时效果仅运行一次。
const initializeAccount = useCallback(async () => {
if (request) {
const user = await request.get('api/user/')
if (user) {
setUser(user.data)
}
}
}, [request])
请注意,如果request
反过来没有稳定的引用,useCallback
将在每个渲染上运行(如上所述(,然后useEffect
也是如此。如果useFetch
是由您编写的,请确保它返回具有稳定引用的对象。如果useFetch
是从库中提供的,则很可能已经如此。
TL;博士
将initializeAccount
包装在useCallback
中,并将其添加到useEffect
的依赖项数组中。
const initializeAccount = useCallback(async () => {
try {
const user = await request.get('api/user/')
const profile = await request.get('api/profile/')
const posts = await request.get('api/posts/')
if (user) {
setUser(user.data)
}
if (profile) {
setProfile(profile.data)
}
if (posts) {
setPosts(posts.data)
}
} catch (e) {
console.log('could not initialize account')
}
}, [request])
useEffect(() => {
initializeAccount()
return () => console.log('unmount')
}, [initializeAccount])