使用异步 componentDidMount() 好吗?

IT技术 reactjs asynchronous react-native
2021-04-13 01:37:27

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

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

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

这有什么问题吗?有没有其他解决方案可以解决这个问题?

6个回答

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

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

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

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

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

  1. async关键字:在typescript,这仅仅是一个代码标记。它做两件事:
    • 强制返回类型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();
    }
    

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

  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,那一次是老用户和老问题。结果也将对用户可见。

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

我想避免问题的一种方法是isFetching: true在组件的状态中使用类似的东西我只在 redux 中使用过它,但我认为它对仅反应状态管理完全有效。虽然它并没有真正解决代码中其他地方更新相同状态的问题......
2021-05-26 01:37:27
当你谈到在挂起的 Promise 之前发生另一个 setState 的问题时,与没有 async/await 语法糖甚至经典回调的 Promise 不一样吗?
2021-05-30 01:37:27
是的!延迟 asetState()总是具有很小的风险。我们应该谨慎行事。
2021-06-13 01:37:27
我同意这一点。事实上,isFetchingflag 的解决方案很常见,尤其是当我们想在前端播放一些动画同时等待后端响应时 ( isFetching: true)。
2021-06-13 01:37:27
如果在卸载组件后执行 setState,则可能会遇到问题
2021-06-16 01:37:27

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


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

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

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

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

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

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>;
  }
}
我们正在使用 React Native 0.63.4 和 React 16.13.1,但我们仍然存在未处理的Promise拒绝未被错误边界捕获的问题。
2021-05-23 01:37:27
是否有针对此报告的问题?如果情况仍然如此,报告它可能很有用...thx
2021-05-28 01:37:27
情况似乎不再如此,至少在这里测试的 React 16.13.1 是这样的:codesandbox.io/s/bold-ellis-n1cid?file=/src/App.js
2021-05-30 01:37:27
实际上,@abernier,如果您关闭代码和框中的错误窗口,您将看到页面仍在呈现。分叉了你的代码盒子并添加了一个错误边界。如果删除asyncfrom componentDidMount,您将看到错误边界捕获错误。如果你把它留在里面,就不会发现错误。只需确保关闭错误消息窗口即可。
2021-06-06 01:37:27
@abernier 我认为这是出于敬意......虽然他们可能可以改进它。我没有提交任何关于此的问题...
2021-06-07 01:37:27

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

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

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

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

componentDidMount() {
    fetch(this.getAuth())
      .then(auth => {
          if (auth) this.checkAuth(auth)
      })
}
也是一个选项@ErikAllik :)
2021-05-24 01:37:27
...或者,只使用async带有awaits 的内函数...?
2021-05-26 01:37:27
@ErikAllik 你碰巧有一个例子吗?
2021-06-05 01:37:27
@PabloRincon smth 就像(async () => { const data = await fetch('foo'); const result = await submitRequest({data}); console.log(result) })()wherefetchsubmitRequest是返回Promise的函数。
2021-06-08 01:37:27
这段代码肯定很糟糕,因为它会吞下 getAuth 函数中发生的任何错误。如果该函数对网络执行某些操作(例如),则必定会出现错误。
2021-06-08 01:37:27

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

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

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

我喜欢这个答案,因为它简洁且有文档支持。您能否添加指向您正在引用的文档的链接。
2021-06-05 01:37:27
这甚至可能是一件好事,例如,如果您在加载资源时显示加载状态,然后在加载完成时显示内容。
2021-06-09 01:37:27

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

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

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