使用异步componentDidMount()好吗?


139

componentDidMount()在React Native中用作异步函数的好习惯还是应该避免呢?

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

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

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


1
“良好做法”是一个见解。它行得通吗?是。
Kraylog '17

2
这里是一个很好的文章,其说明了为什么异步的await是在承诺一个不错的选择hackernoon.com/...
Shubham卡特里

只需使用redux-thunk即可解决问题
Tilak Maddy

@TilakMaddy为什么您认为每个React应用都使用Redux?
Mirakurun

@Mirakurun为什么当我过去经常问普通的javascript问题时,为什么整个堆栈溢出都假设我使用jQuery?
Tilak Maddy

Answers:


162

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

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

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

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

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

  1. async关键字:在打字稿,这仅仅是一个代码标记。它做两件事:
    • 强制将返回类型Promise<void>改为void。如果您明确指定返回类型为非承诺(例如:void),则打字稿将向您吐一个错误。
    • 允许您await在方法内部使用关键字。
  2. 返回类型从更改voidPromise<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();
    }

现在,它们怎么会引起麻烦?

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

    调用到具有方法的返回类型的Promise<void>await关键字将没有从调用具有返回类型的一个区别void

  3. 由于没有生命周期方法,因此componentDidMount()延迟执行似乎很安全。但是有一个陷阱。

    假设上述操作this.setState({users, questions});将在10秒后执行。在延迟的时间中间,另一个...

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

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

=> asynccomponentDidMount()method 一起使用非常安全(我不确定大约100%)。我是它的忠实拥护者,到目前为止,我还没有遇到任何让我头疼的问题。


当您谈论在未决的Promise之前发生另一个setState的问题时,如果没有async / await语法糖甚至经典的回调,Promise会不是一样吗?
Clafou '18

3
是! 延迟交易setState()总会有很小的风险。我们应该谨慎行事。
铜ĐứcHIEU

我猜想避免问题的一种方法是isFetching: true在组件状态内部使用类似的东西。我只将它与redux一起使用,但我想它对仅反应状态管理是完全有效的。虽然它并不能真正解决相同状态在代码中其他地方更新的问题……
Clafou

1
我同意这一点。实际上,isFetching标志解决方案非常普遍,尤其是当我们想在等待后端响应(isFetching: true)的同时在前端播放一些动画时。
铜ĐứcHIEU

3
如果在卸载组件后执行setState,可能会遇到问题
Eliezer Steinbock

18

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


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

因此,如果您编写如下内容:

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

那么您的错误将被error boundry捕获,并且您可以对其进行处理并显示优美的消息。

如果我们这样更改代码:

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

这等效于:

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

那么您的错误就会被默默地吞噬。对你感到羞耻,React ...

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

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

或像这样:

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

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

  1. 捕获错误,使错误处理程序更改组件状态
  2. 如果状态指示错误,则将其从render方法中抛出

例:

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>;
  }
}

有没有为此报告的问题?如果仍然如此,报告可能会有用... thx
abernier

@abernier我认为这是按设计的……尽管他们可能会改进它。我没有对此提出任何问题……
CF

1
似乎不再如此了,至少在这里测试了React 16.13.1:codesandbox.io/s/bold-ellis-n1cid?file =
src

9

您的代码很好,对我来说也很容易理解。看到这个 Dale Jefferson的这篇文章,其中显示了一个异步componentDidMount示例,并且看起来也很不错。

但是有些人会说,阅读代码的人可能会认为React会使用返回的诺言做某事。

因此,对此代码的解释以及是否是一个好的实践都是非常个人的。

如果您需要其他解决方案,则可以使用promises。例如:

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

3
...或者也可以只asyncawaits中使用内联函数...?
埃里克·卡普伦

还有一个选项@ErikAllik :)
Tiago Alves

@ErikAllik您碰巧有个例子吗?
Pablo Rincon

1
@PabloRincon就像(async () => { const data = await fetch('foo'); const result = await submitRequest({data}); console.log(result) })()where fetchsubmitRequest是返回promise的函数一样。
埃里克·卡普伦

此代码绝对不好,因为它将吞没getAuth函数中发生的任何错误。并且,如果该功能对网络起作用(例如),则必须预料到错误。
CF

6

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

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

如果使用async componentDidMount,则会失去此功能:浏览器更新屏幕后,将进行另一个渲染。但是imo,如果您正在考虑使用异步操作(例如获取数据),则无法避免浏览器将屏幕更新两次。在另一个世界中,无法在浏览器更新屏幕之前暂停componentDidMount


1
我喜欢这个答案,因为它简洁明了并受文档支持。能否请您添加指向您所引用文档的链接。
–theUtherSide

这甚至可能是一件好事,例如,如果您在资源加载时显示加载状态,然后在完成时显示内容。
Hjulle

3

更新:

(我的构建:React 16,Webpack 4,Babel 7):

使用Babel 7时,您会发现:

使用此模式...

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

您将遇到以下错误...

未捕获的ReferenceError:未定义regeneratorRuntime

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

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

如果由于某种原因您不希望安装上述软件包(babel-plugin-transform-runtime),那么您将希望遵循Promise模式...

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

3

我认为只要您知道自己在做什么就可以。但这可能会造成混乱,因为async componentDidMount()componentWillUnmount运行并且卸载组件后仍可以运行。

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

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


2

实际上,建议在ComponentDidMount中进行异步加载,因为React从传统的生命周期方法(componentWillMount,componentWillReceiveProps,componentWillUpdate)转移到了Async Rendering。

这篇博客文章对于解释为什么这样做安全并在ComponentDidMount中提供异步加载示例非常有帮助:

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


3
异步渲染实际上与使生命周期显式异步无关。这实际上是一种反模式。推荐的解决方案是从生命周期方法中实际调用异步方法
Clayton Ray

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.