setState会导致无限循环,即使它在渲染功能之外

时间:2020-09-17 09:47:47

标签: javascript reactjs react-hooks

我目前正在使用openweatherapp API开发一个简单的气象应用程序。该应用程序旨在从两个端点获取数据:一个端点返回您所在城市的当前天气,另一个端点返回未来5天的天气预报。该应用程序还应该在60秒后触发一个事件,以重新获取数据。这就是我尝试构建解决方案的方式:

在App.js中,我正在获取数据,然后将其作为道具传递给另外两个组件,一个组件处理当前天气,另一个组件用于天气预报。在CurrentWeatherForecast组件中,我还启动了使用钩子每秒更新状态的函数。当计时器达到60秒时,我正在调用“ handleRefresh”函数,该函数已作为App.js的支持传递给了我。 (在App.js中是实际更新发生的位置)。 “ handleRefresh”函数位于App.js的render方法之外,并且它会更新“ step”变量,该变量随后应导致组件重新呈现并重新获取数据。问题在于,在调用setState时,该函数会导致一个无限循环,由于该函数位于render方法之外,因此我不明白为什么会发生这种情况。我将在下面发布我的代码。

import React, { Component } from "react";
import { CurrentWeatherForecast } from "./components/CurrentWeatherForecast";
import { NextDaysWeatherForecast } from "./components/NextDaysWeatherForecast";

export class App extends Component {
constructor(props) {
    super(props);
    this.state = {
        currentWeather: [],
        nextDaysWeather: [],
        step: 0,
    };
}
componentDidMount() {
    const { step } = this.state;
    var currentWeather;
    var nextDaysWeather; // step is used to indicate wether I want to fetch data or not
    if (step === 0) {
        fetch(
            "https://api.openweathermap.org/data/2.5/weather?q=London&appid=1fc71092a81b329e8ce0e1ae88ef0fb7"
        )
            .then((response) => {
                const contentType = response.headers.get("content-type");
                if (
                    !contentType ||
                    !contentType.includes("application/json")
                ) {
                    throw new TypeError("No JSON data!");
                }
                return response.json();
            })
            .then((data) => {
                currentWeather = data;
            })
            .catch((error) => console.error(error));
        fetch(
            "https://api.openweathermap.org/data/2.5/forecast?q=London&appid=1fc71092a81b329e8ce0e1ae88ef0fb7"
        )
            .then((response) => {
                const contentType = response.headers.get("content-type");
                if (
                    !contentType ||
                    !contentType.includes("application/json")
                ) {
                    throw new TypeError("No JSON data!");
                }
                return response.json();
            })
            .then((data) => {
                let requiredData = data.list.slice(0, 5);
                nextDaysWeather = requiredData;
            })
            .catch((error) => console.error(error));
        let f = setTimeout(() => {
            this.setState({
                currentWeather: currentWeather,
                nextDaysWeather: nextDaysWeather,
                step: 1, // updating step to 1 after fetching the data
            });
        }, 1000);
    }
}

handleRefresh = () => {
    const { step } = this.state;
    console.log(step);
    this.setState({ step: 0 }); // updating the step to 0 this causes the infinite loop
};

render() {
    const { currentWeather, nextDaysWeather } = this.state;
    return (
        <div>
            <CurrentWeatherForecast
                currentWeather={currentWeather}
                handleRefresh={this.handleRefresh}
            />
            <NextDaysWeatherForecast nextDaysWeather={nextDaysWeather} />
        </div>
    );
}
}

export default App;

这是在App.js中,因为目前为空,请忽略NextDaysWeatherForecast组件

import React, { useEffect, useState } from "react";

export const CurrentWeatherForecast = (props) => {
const { currentWeather } = props;
const [progressValue, setValue] = useState(0);

useEffect(() => {
    const interval = setInterval(() => {
        setValue((progressValue) =>
            progressValue < 61 ? progressValue + 1 : (progressValue = 0)
        );
    }, 1000);
    return () => clearInterval(interval);
}, []);
if (progressValue === 60) {
    props.handleRefresh(); // calling the handleRefresh function passed from App.js
}

return (
    <div>
        <label htmlFor="file">Downloading progress:</label>
        <progress id="file" value={progressValue} max="60">
            {progressValue}%
        </progress>
    </div>
);
};

这是NextWeatherForecast组件,我在其中初始化计时器,然后调用作为道具传递的“ handleRefresh”函数。

先谢谢大家!

2 个答案:

答案 0 :(得分:2)

看看这个效果阶段和渲染阶段代码,并尝试猜测出什么问题。

useEffect(() => {
    const interval = setInterval(() => {
        setValue((progressValue) =>
            progressValue < 61 ? progressValue + 1 : (progressValue = 0)
        );
    }, 1000);
    return () => clearInterval(interval);
}, []);
if (progressValue === 60) {
    props.handleRefresh(); // calling the handleRefresh function passed from App.js
}

尤其是这个气味闻起来像是溢出了:在渲染阶段调用了一个引起渲染的函数(而且我们知道handleRefresh会导致渲染。

if (progressValue === 60) {
    props.handleRefresh(); // calling the handleRefresh function passed from App.js
}

现在,让我们寻找应该阻止溢出的内容(也就是说,它尝试将progressValue设置为60以外的其他值(一旦设置为60)。

这里是:

progressValue < 61 ? progressValue + 1 : (progressValue = 0)

除了,这仅每1000ms触发一次。这意味着一秒钟您的组件将陷入重新渲染循环中。一旦将其设置为60,React就会疯狂地开始渲染,并且在很短的时间内就超过了渲染限制,而progressValue距离设置为0还有很多毫秒。


一个示例解决方案是检查progressValue === 60是否有其他效果。

export const CurrentWeatherForecast = (props) => {
    const { currentWeather } = props;
    const [progressValue, setValue] = useState(0);

    useEffect(() => {
        const interval = setInterval(() => {
            setValue(prevProgressValue => prevProgressValue === 60 ? 0 : prevProgressValue + 1);
        }, 1000);
        return () => clearInterval(interval);
    }, []);

    useEffect(() => progressValue === 60 && props.handleRefresh(), [progressValue]);

    return (
        <div>
            <label htmlFor="file">Downloading progress:</label>
            <progress id="file" value={progressValue} max="60">
                {progressValue}%
        </progress>
        </div>
    );
};

答案 1 :(得分:2)

尝试一下:

import React, { useEffect, useState } from "react";

export const CurrentWeatherForecast = ({ currentWeather }) => {

useEffect(() => {
    const interval = setInterval(() => {
         props.handleRefresh();
    }, 60000);
    return () => clearInterval(interval);
}, []);
   
return (
    <div>
        your codes goes here...
    </div>
);
};