reactjs 使用异步 componentDidMount() 好吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/47970276/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-06 04:19:18  来源:igfitidea点击:

Is using async componentDidMount() good?

reactjsasynchronousreact-native

提问by Mirakurun

Is using componentDidMount()as an async function good practice in React Native or should I avoid it?

使用componentDidMount()作为一个异步函数的良好做法作出反应本地还是应该避免呢?

I need to get some info from AsyncStoragewhen the component mounts, but the only way I know to make that possible is to make the componentDidMount()function async.

我需要从AsyncStorage组件安装时获取一些信息,但我知道的唯一方法是使componentDidMount()函数异步。

async componentDidMount() {
    let auth = await this.getAuth();
    if (auth) 
        this.checkAuth(auth);
}

Is there any problem with that and are there any other solutions to this problem?

这有什么问题吗,这个问题还有其他解决方案吗?

回答by Cù ??c Hi?u

Let's start by pointing out the differences and determining how it could cause troubles.

让我们首先指出差异并确定它如何引起麻烦。

Here is the code of async and "sync" componentDidMount()life-cycle method:

这是异步和“同步”componentDidMount()生命周期方法的代码:

// This is typescript code
componentDidMount(): void { /* do something */ }

async componentDidMount(): Promise<void> {
    /* do something */
    /* You can use "await" here */
}

By looking at the code, I can point out the following differences:

通过查看代码,我可以指出以下差异:

  1. The asynckeywords: In typescript, this is merely a code marker. It does 2 things:
    • Force the return type to be Promise<void>instead of void. If you explicitly specify the return type to be non-promise (ex: void), typescript will spit an error at you.
    • Allow you to use awaitkeywords inside the method.
  2. The return type is changed from voidto Promise<void>
    • It means you can now do this:
      async someMethod(): Promise<void> { await componentDidMount(); }
  3. You can now use awaitkeyword inside the method and temporarily pause its execution. Like this:

    async componentDidMount(): Promise<void> {
        const users = await axios.get<string>("http://localhost:9001/users");
        const questions = await axios.get<string>("http://localhost:9001/questions");
    
        // Sleep for 10 seconds
        await new Promise(resolve => { setTimeout(resolve, 10000); });
    
        // This line of code will be executed after 10+ seconds
        this.setState({users, questions});
        return Promise.resolve();
    }
    
  1. async关键字:在打字稿,这仅仅是一个代码标记。它做两件事:
    • 强制返回类型Promise<void>代替void. 如果您明确指定返回类型为 non-promise(例如:void),typescript 会向您吐出错误。
    • 允许您await在方法内使用关键字。
  2. 返回类型由void改为Promise<void>
    • 这意味着您现在可以这样做:
      async someMethod(): Promise<void> { await componentDidMount(); }
  3. 您现在可以await在方法中使用关键字并暂时暂停其执行。像这样:

    async componentDidMount(): Promise<void> {
        const users = await axios.get<string>("http://localhost:9001/users");
        const questions = await axios.get<string>("http://localhost:9001/questions");
    
        // Sleep for 10 seconds
        await new Promise(resolve => { setTimeout(resolve, 10000); });
    
        // This line of code will be executed after 10+ seconds
        this.setState({users, questions});
        return Promise.resolve();
    }
    

Now, how could they cause troubles?

现在,他们怎么会惹事呢?

  1. The asynckeyword is absolutely harmless.
  2. I cannot imagine any situation in which you need to make a call to the componentDidMount()method so the return type Promise<void>is harmless too.

    Calling to a method having return type of Promise<void>without awaitkeyword will make no difference from calling one having return type of void.

  3. Since there is no life-cycle methods after componentDidMount()delaying its execution seems pretty safe. But there is a gotcha.

    Let's say, the above this.setState({users, questions});would be executed after 10 seconds. In the middle of the delaying time, another ...

    this.setState({users: newerUsers, questions: newerQuestions});

    ... were successfully executed and the DOM were updated. The result were visible to users. The clock continued ticking and 10 seconds elapsed. The delayed this.setState(...)would then execute and the DOM would be updated again, that time with old users and old questions. The result would also be visible to users.

  1. async关键字是绝对无害的。
  2. 我无法想象您需要调用该componentDidMount()方法以便返回类型Promise<void>也无害的任何情况。

    调用返回类型为Promise<void>withoutawait关键字的方法与调用返回类型为 的方法没有区别void

  3. 由于componentDidMount()延迟执行后没有生命周期方法似乎很安全。但有一个问题。

    比方说,上面的代码this.setState({users, questions});会在 10 秒后执行。在延迟的时间中间,另一个......

    this.setState({users: newerUsers, questions: newerQuestions});

    ... 已成功执行并更新 DOM。结果对用户可见。时钟继续滴答作响,10 秒过去了。然后this.setState(...)将执行延迟并再次更新 DOM,那一次是老用户和老问题。结果也将对用户可见。

=> It is pretty safe (I'm not sure about 100%) to use asyncwith componentDidMount()method. I'm a big fan of it and so far I haven't encountered any issues which give me too much headache.

=>asynccomponentDidMount()方法一起使用非常安全(我不确定是否为 100%)。我是它的忠实粉丝,到目前为止我还没有遇到任何让我头疼的问题。

回答by C-F

Update April 2020:The issue seems to be fixed in latest React 16.13.1, see this sandbox example. Thanks to @abernier for pointing this out.

2020 年 4 月更新:该问题似乎已在最新的 React 16.13.1 中得到解决,请参阅此沙箱示例。感谢@abernier 指出这一点。



I have made some research, and I have found one important difference: React does not process errors from async lifecycle methods.

我做了一些研究,我发现了一个重要的区别: React 不处理来自异步生命周期方法的错误。

So, if you write something like this:

所以,如果你写这样的东西:

componentDidMount()
{
    throw new Error('I crashed!');
}

then your error will be caught by the error boundry, and you can process it and display a graceful message.

那么您的错误将被错误边界捕获,您可以对其进行处理并显示一条优雅的消息。

If we change the code like this:

如果我们像这样更改代码:

async componentDidMount()
{
    throw new Error('I crashed!');
}

which is equivalent to this:

这相当于:

componentDidMount()
{
    return Promise.reject(new Error('I crashed!'));
}

then your error will be silently swallowed. Shame on you, React...

那么你的错误将被默默地吞噬。为你感到羞耻,反应...

So, how do we process errors than? The only way seems to be explicit catch like this:

那么,我们如何处理错误呢?唯一的方法似乎是这样的显式捕获:

async componentDidMount()
{
    try
    {
         await myAsyncFunction();
    }
    catch(error)
    {
        //...
    }
}

or like this:

或者像这样:

componentDidMount()
{
    myAsyncFunction()
    .catch(()=>
    {
        //...
    });
}

If we still want our error to reach the error boundary, I can think about the following trick:

如果我们仍然希望我们的错误达到错误边界,我可以考虑以下技巧:

  1. Catch the error, make the error handler change the component state
  2. If the state indicates an error, throw it from the rendermethod
  1. 捕获错误,使错误处理程序改变组件状态
  2. 如果状态指示错误,则将其从render方法中抛出

Example:

例子:

class BuggyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
  }

  buggyAsyncfunction(){ return Promise.reject(new Error('I crashed async!'));}

  async componentDidMount() {
    try
    {
      await this.buggyAsyncfunction();
    }
    catch(error)
    {
        this.setState({error: error});
    }
  }

  render() {
    if(this.state.error)
        throw this.state.error;

    return <h1>I am OK</h1>;
  }
}

回答by Tiago Alves

Your code is fine and very readable to me. See this Dale Jefferson's articlewhere he shows an async componentDidMountexample and looks really good as well.

你的代码很好,对我来说可读性很强。请参阅Dale Jefferson 的这篇文章,其中展示了一个异步componentDidMount示例并且看起来也非常好。

But some people would say that a person reading the code may assume that React does something with the returned promise.

但是有些人会说,阅读代码的人可能会认为 React 对返回的 Promise 做了一些事情。

So the interpretation of this code and if it is a good practice or not is very personal.

所以这段代码的解释以及它是否是一个好的做法是非常个人的。

If you want another solution, you could use promises. For example:

如果你想要另一个解决方案,你可以使用promises。例如:

componentDidMount() {
    fetch(this.getAuth())
      .then(auth => {
          if (auth) this.checkAuth(auth)
      })
}

回答by Lu Tran

When you use componentDidMountwithout asynckeyword, the doc say this:

当您使用componentDidMountwithoutasync关键字时,文档会这样说:

You may call setState() immediately in componentDidMount(). It will trigger an extra rendering, but it will happen before the browser updates the screen.

您可以在 componentDidMount() 中立即调用 setState()。它会触发额外的渲染,但它会在浏览器更新屏幕之前发生。

If you use async componentDidMountyou will loose this ability: another render will happen AFTER the browser update the screen. But imo, if you are thinking about using async, such as fetching data, you can not avoid the browser will update the screen twice. In another world, it is not possible to PAUSE componentDidMount before browser update the screen

如果你使用async componentDidMount你会失去这个能力:在浏览器更新屏幕后会发生另一个渲染。但是imo,如果您正在考虑使用异步,例如获取数据,则无法避免浏览器将更新屏幕两次。在另一个世界,在浏览器更新屏幕之前暂停 componentDidMount 是不可能的

回答by dosentmatter

I think it's fine as long as you know what you're doing. But it can be confusing because async componentDidMount()can still be running after componentWillUnmounthas run and the component has unmounted.

我认为只要你知道自己在做什么就可以了。但它可能会令人困惑,因为async componentDidMount()componentWillUnmount已运行且组件已卸载后仍然可以运行。

You may also want to start both synchronous and asynchronous tasks inside componentDidMount. If componentDidMountwas async, you would have to put all the synchronous code before the first await. It might not be obvious to someone that the code before the first awaitruns synchronously. In this case, I would probably keep componentDidMountsynchronous but have it call sync and async methods.

您可能还想在componentDidMount. 如果componentDidMount是异步的,则必须将所有同步代码放在第一个await. 第一次await同步运行之前的代码对某些人来说可能并不明显。在这种情况下,我可能会保持componentDidMount同步但让它调用同步和异步方法。

Whether you choose async componentDidMount()vs sync componentDidMount()calling asyncmethods, you have to make sure you clean up any listeners or async methods that may still be running when the component unmounts.

无论您选择还是async componentDidMount()同步componentDidMount()调用async方法,您都必须确保清除组件卸载时可能仍在运行的所有侦听器或异步方法。

回答by Chad

Update:

更新:

(My build: React 16, Webpack 4, Babel 7):

(我的版本:React 16、Webpack 4、Babel 7):

When using Babel 7 you'll discover:

使用 Babel 7 时,您会发现:

Using this pattern...

使用这种模式...

async componentDidMount() {
    try {
        const res = await fetch(config.discover.url);
        const data = await res.json();
        console.log(data);
    } catch(e) {
        console.error(e);
    }
}

you will run into the following error...

你会遇到以下错误...

Uncaught ReferenceError: regeneratorRuntime is not defined

未捕获的 ReferenceError:未定义 regeneratorRuntime

In this case you will need to install babel-plugin-transform-runtime

在这种情况下,您需要安装babel-plugin-transform-runtime

https://babeljs.io/docs/en/babel-plugin-transform-runtime.html

https://babeljs.io/docs/en/babel-plugin-transform-runtime.html

If for some reason you do not wish to install the above package (babel-plugin-transform-runtime) then you will want to stick to the Promise pattern...

如果由于某种原因您不想安装上述包(babel-plugin-transform-runtime),那么您将要坚持使用 Promise 模式...

componentDidMount() {
    fetch(config.discover.url)
    .then(res => res.json())
    .then(data => {
        console.log(data);
    })
    .catch(err => console.error(err));
}

回答by DannyMoshe

Actually, async loading in ComponentDidMount is a recommended design patternas React moves away from legacy lifecycle methods (componentWillMount, componentWillReceiveProps, componentWillUpdate) and on to Async Rendering.

实际上,ComponentDidMount 中的异步加载是一种推荐的设计模式,因为 React 从传统的生命周期方法(componentWillMount、componentWillReceiveProps、componentWillUpdate)转向异步渲染。

This blog post is very helpful in explaining why this is safe and providing examples for async loading in ComponentDidMount:

这篇博文非常有助于解释为什么这是安全的,并提供了在 ComponentDidMount 中异步加载的示例:

https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html

https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html

回答by Gustavo Miguel

I like to use something like this

我喜欢用这样的东西

componentDidMount(){
   const result = makeResquest()
}
async makeRequest(){
   const res = await fetch(url);
   const data = await res.json();
   return data
}