React: 类方法并不总是被执行?

huangapple go评论74阅读模式
英文:

React: Class method isn't always being ran?

问题

这是一个加载外部API中应用程序列表并针对每个应用程序运行附加请求的类组件。

它能够成功获取应用程序列表,但是只有在某些情况下才会调用checkPing来对应用程序进行"ping"。

以下是该组件的代码:

import axios from "axios";
import React from "react";

class Applications extends React.Component<any, any> {
    constructor() {
        super({});

        this.state = {
            applications: []
        }
    }

    async checkPing(applicationId: bigint) {
        //
    }

    async getApplications() {
        var component = this;

        await axios.get(process.env.REACT_APP_API_URL + '/applications')
            .then(async function (response) {
                await component.setState({
                    applications: response.data,
                });
            })
            .catch(function (error) {
                // 处理错误
                console.log(error);
            })
            .finally(function () {
                // 总是执行
            });

        for (let i = 0; i < component.state.applications.length; i++) {
            await component.checkPing(component.state.applications[i].id);
        }
    }

    async componentDidMount() {
        await this.getApplications();
    }

    render() {
        return (<div></div>)
    }
}

export default Applications;

希望这对你有所帮助。

英文:

I have a class component which loads a list of applications from an external API. It then runs an additional request for each application to ping it.

It fetches the applications fine. However, it only sometimes pings the applications (calls checkPing).

Here is the component:

import axios from &quot;axios&quot;;
import React from &quot;react&quot;;

class Applications extends React.Component&lt;any, any&gt; {
    constructor() {
        super({});

        this.state = {
            applications: []
        }
    }

    async checkPing(applicationId : bigint) {
        //
    }

    async getApplications() {
        var component = this;

        await axios.get(process.env.REACT_APP_API_URL + &#39;/applications&#39;)
            .then(async function (response) {
                await component.setState({
                    applications: response.data,
                });
            })
            .catch(function (error) {
                // handle error
                console.log(error);
            })
            .finally(function () {
                // always executed
            });

        for (let i = 0; i &lt; component.state.applications.length; i++) {
            await component.checkPing(component.state.applications[i].id);
        }
    }

    async componentDidMount() {
        await this.getApplications();
    }

    render() {
        return (&lt;div&gt;&lt;/div&gt;)
    }
}

export default Applications;

答案1

得分: 2

如在评论中提到的,

你可以将你的for循环移到then块内部,或者你可以使用componentDidUpdate生命周期钩子。axios调用是异步的,而你的for循环当前并不等待它完成。

以下是使用componentDidUpdate的示例:

import React from "react";
import axios from "axios";
import "./styles.css";

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      applications: []
    };
  }

  async checkPing(applicationId) {
    console.log("pinging " + applicationId);
  }

  async getApplications() {
    var component = this;

    try {
      const response = await axios.get("./data.json");
      component.setState({ applications: response.data });
    } catch (error) {
      // 处理错误
      console.log(error);
    }
  }

  componentDidMount() {
    this.getApplications();
  }

  componentDidUpdate() {
    for (let i = 0; i < this.state.applications.length; i++) {
      this.checkPing(this.state.applications[i].id);
    }
  }

  render() {
    return <div>HERE</div>;
  }
}

export default App;

如果你不想使用componentDidUpdate,那么你可以将for循环移到then调用内部,像这样:

import React from "react";
import axios from "axios";
import "./styles.css";

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      applications: []
    };
  }

  async checkPing(applicationId) {
    console.log("pinging " + applicationId);
  }

  async getApplications() {
    var component = this;

    try {
      const response = await axios.get("./data.json");
      component.setState({ applications: response.data });
      for (let i = 0; i < response.data.length; i++) {
        this.checkPing(response.data[i].id);
      }
    } catch (error) {
      // 处理错误
      console.log(error);
    }
  }

  componentDidMount() {
    this.getApplications();
  }

  render() {
    return <div>HERE</div>;
  }
}

export default App;
英文:

As mentioned in the comment,

You either need to move your for loop inside then block or you can use componentDidUpdate lifecycle hook. axios call is asynchronous and your for loop is currently not waiting for it to complete.

Here is the example of using componentDidUpdate

import React from &quot;react&quot;;
import axios from &quot;axios&quot;;
import &quot;./styles.css&quot;;

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      applications: []
    };
  }

  async checkPing(applicationId) {
    console.log(&quot;pinging &quot; + applicationId);
  }

  async getApplications() {
    var component = this;

    try {
        const response = await axios.get(&quot;./data.json&quot;);
        component.setState({ applications: response.data });
    } catch (error) {
        // handle error
        console.log(error);
    }
  }

  componentDidMount() {
    this.getApplications();
  }

  componentDidUpdate() {
    for (let i = 0; i &lt; this.state.applications.length; i++) {
      this.checkPing(this.state.applications[i].id);
    }
  }

  render() {
    return &lt;div&gt;HERE&lt;/div&gt;;
  }
}

export default App;

If you do not want to use componentDidUpdate then you can move for loop in then call like this.

import React from &quot;react&quot;;
import axios from &quot;axios&quot;;
import &quot;./styles.css&quot;;

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      applications: []
    };
  }

  async checkPing(applicationId) {
    console.log(&quot;pinging &quot; + applicationId);
  }

  async getApplications() {
    var component = this;

    try {
        const response = await axios.get(&quot;./data.json&quot;);
        component.setState({ applications: response.data });
        for (let i = 0; i &lt; response.data.length; i++) {
          this.checkPing(response.data[i].id);
        }
    } catch (error) {
        // handle error
        console.log(error);
    }
  }

  componentDidMount() {
    this.getApplications();
  }

  render() {
    return &lt;div&gt;HERE&lt;/div&gt;;
  }
}

export default App;

答案2

得分: 1

以下是翻译好的部分:

"setState()"将排队状态更改而不会直接应用它。 "setState()"不会返回一个Promise。这意味着"await component.setState(...)"是无效的。了解React何时完成状态更新的唯一方法是使用componentDidUpdate()钩子。

使用您的示例实现此钩子可能如下所示:

import axios from "axios";
import React from "react";

class Applications extends React.Component<any, any> {
    constructor(props) {
        super(props);

        this.state = {
            isLoading: true,
            error: null,
            applications: [],
        }
    }

    async checkPing(applicationId: bigint) {
        // ...
    }

    async getApplications() {
        // 在异步上下文中,无需使用"then()","catch()"或"finally()"。
        // 我们可以使用相应的关键字代替。
        try {
            const response = await axios.get(process.env.REACT_APP_API_URL + '/applications');
            this.setState({ applications: response.data });
        } catch (error) {
            // 处理错误
            this.setState({ error });
        } finally {
            // 总是执行
            this.setState({ isLoading: false });
        }
    }

    // 内容也可以放在"componentDidMount()"中,而不是在它自己的方法中。
    async pingApplications() {
        for (const application of this.state.applications) {
            await this.checkPing(application.id);
        }
    }

    componentDidMount() {
        this.getApplications();
    }

    componentDidUpdate(prevProps, prevState) {
        if (this.state.isLoading) return;
        if (this.state.error) return;

        this.pingApplications();
    }

    render() {
        const { isLoading, error, applications } = this.state;

        if (isLoading) return (
            <div className="loading">loading...</div>
        );

        if (error) return (
            <div className="error">{error.message}</div>
        );

        return (
            <div>content</div>
        );
    }
}

export default Applications;
英文:

The main issue here is probably that setState() will queue the state change and not directly apply it. setState() does not return a promise. Meaning that await component.setState(...) is useless. The only way to know when React is done updating the state is to use the componentDidUpdate() hook.

An implementation of this hook, using your example might look like this:

import axios from &quot;axios&quot;;
import React from &quot;react&quot;;

class Applications extends React.Component&lt;any, any&gt; {
    constructor(props) {
        super(props);

        this.state = {
            isLoading: true,
            error: null,
            applications: [],
        }
    }

    async checkPing(applicationId : bigint) {
        // ...
    }

    async getApplications() {
        // There is no need to work with `then()`, `catch()` or `finally()`
        // when we are within async context. We can use the respective
        // keywords instead.
        try {
            const response = await axios.get(process.env.REACT_APP_API_URL + &#39;/applications&#39;);
            this.setState({ applications: response.data });
        } catch (error) {
            // handle error
            this.setState({ error });
        } finally {
            // always executed
            this.setState({ isLoading: false });
        }
    }

    // Content can also be placed in `componentDidMount()` instead of
    // its own method.
    async pingApplications() {
        for (const application of this.state.applications) {
            await this.checkPing(application.id);
        }
    }

    componentDidMount() {
        this.getApplications();
    }

    componentDidUpdate(prevProps, prevState) {
        if (this.state.isLoading) return;
        if (this.state.error) return;

        this.pingApplications();
    }

    render() {
        const { isLoading, error, applications } = this.state;

        if (isLoading) return (
            &lt;div className=&quot;loading&quot;&gt;loading...&lt;/div&gt;
        );

        if (error) return (
            &lt;div className=&quot;error&quot;&gt;{error.message}&lt;/div&gt;
        );

        return (
            &lt;div&gt;content&lt;/div&gt;
        );
    }
}

export default Applications;

huangapple
  • 本文由 发表于 2023年3月31日 21:04:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/75898891.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定