奇怪的useEffect无限循环

通过我们用来处理 apiCalls 的钩子看到一些奇怪的事情,其中​​ api 响应结构的突然变化触发了我们应用程序中的无限请求循环。我已经将它缩小到重现错误的基本要求。

import React, {useEffect, useState, useMemo} from 'react';

const mockedApiCall = (resolvedValue, time) =>
    new Promise((resolve, reject) => setTimeout(() => resolve(resolvedValue), time));

const useHook = (query) => {
    const [error, setError] = useState(null);
    const [data, setData] = useState([]);

    useEffect(() => {
        const asyncOperation = async () => {
            const response = await mockedApiCall([], 1000);

            // Testing something i know will fail:
            const mappedData = response.data.map(a => a);

            // if the above didn't fail we would update data
            setData(mappedData);
        };

        // Clear old errors
        setError(null);
        // trigger operation
        asyncOperation().catch(e => {
            console.log('fail');
            setError('Some error');
        });
    }, [query]);

    return [data, error];
}


const HookIssue = props => {
    const [data, error] = useHook({foo: 'bar'}); // Creates infinite loop
    //const [data, error] = useHook('foo') // only fails a single time

    // Alternative solution that also doesn't create infinite loop
    // const query = useMemo(() => ({foo: 'bar'}), []);
    // const [data, error] = useHook(query);


    return null;
};

export default HookIssue;

有谁知道这里发生了什么?这个钩子应该只监听查询变量的变化,但在某些情况下会陷入无限循环。

编辑:为了给这个问题添加一些更奇怪的东西,如果我删除 useEffect 中的两个 setError 调用中的一个(或两个),它也会防止无限循环。

回答

你得到这个循环是因为你将一个对象传递给useHook(). 对象是引用类型——也就是说,它们不是由它们的值定义的。由于这个原因,以下语句的计算结果为 false:

{ foo: "bar" } === { foo: "bar" }

每次HookIssue渲染时,它都会创建一个{ foo: "bar" }. 这是useEffect()在依赖数组中传递给的,但是因为对象是引用类型 - 每个渲染上的每个对象都有不同的引用 - React 将始终“看到”该query值已更改。

这意味着每次 asyncOperation完成时,都会导致重新渲染HookIssue,这将导致触发另一个效果。

要解决此问题,您需要将值类型字符串传递给依赖项数组,或者您需要query通过使用useMemo().


以上是奇怪的useEffect无限循环的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>