【问题标题】:Is using async componentDidMount() good?使用异步 componentDidMount() 好吗?
【发布时间】:2018-06-06 19:20:15
【问题描述】:

在 React Native 中使用 componentDidMount() 作为异步函数的良好做法还是应该避免它?

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

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

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

【问题讨论】:

  • “良好做法”是一个见仁见智的问题。它有效吗?是的。
  • 这是一篇很好的文章,它说明了为什么 async await 是一个比 promise 更好的选择 hackernoon.com/…
  • 使用 redux-thunk 就可以解决问题
  • @TilakMaddy 为什么你认为每个 React 应用都使用 redux?
  • @Mirakurun 为什么当我过去问普通的javascript问题时,整个堆栈溢出都假设我使用jQuery?

标签: reactjs asynchronous react-native


【解决方案1】:

您的代码很好,对我来说非常易读。请参阅此Dale Jefferson's article,其中他展示了一个异步componentDidMount 示例,看起来也非常好。

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

因此,对这段代码的解释以及它是否是一种好的做法是非常个人化的。

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

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

【讨论】:

  • ...或者,也可以使用内联 async 函数和 awaits 内...?
  • 也是一个选项@ErikAllik :)
  • @ErikAllik 你有例子吗?
  • @PabloRincon smth like (async () => { const data = await fetch('foo'); const result = await submitRequest({data}); console.log(result) })() 其中fetchsubmitRequest 是返回承诺的函数。
  • 这段代码肯定是坏的,因为它会吞下 getAuth 函数中发生的任何错误。如果函数对网络做了某些事情(例如),那么肯定会出现错误。
【解决方案2】:

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

这里是async和“sync”componentDidMount()生命周期方法的代码:

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

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

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

  1. async 关键字:在打字稿中,这只是一个代码标记。它做了两件事:
    • 强制返回类型为Promise&lt;void&gt; 而不是void。如果您明确指定返回类型为非承诺(例如:void),打字稿将向您吐出一个错误。
    • 允许你在方法中使用await关键字。
  2. 返回类型从void更改为Promise&lt;void&gt;
    • 这意味着您现在可以这样做了:
      async someMethod(): Promise&lt;void&gt; { 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&lt;void&gt; 也是无害的。

    调用返回类型为Promise&lt;void&gt; 且不带await 关键字的方法与调用返回类型为void 的方法没有区别。

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

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

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

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

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

【讨论】:

  • 当你谈到另一个 setState 在一个待处理的 Promise 之前发生的问题时,如果没有 async/await 语法糖甚至是经典的回调,Promise 不也是一样吗?
  • 是的!延迟setState() 总是有很小的风险。我们应该谨慎行事。
  • 我想避免问题的一种方法是在组件的状态中使用isFetching: true 之类的东西。我只将它与 redux 一起使用,但我认为它对于仅反应状态管理是完全有效的。虽然它并没有真正解决代码中其他地方更新相同状态的问题...
  • 我同意这一点。事实上,isFetching 标志解决方案很常见,尤其是当我们想在前端播放一些动画同时等待后端响应时(isFetching: true)。
  • 在组件卸载后执行 setState 会遇到问题
【解决方案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/en/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));
}

【讨论】:

    【解决方案4】:

    当您使用 componentDidMount 而不使用 async 关键字时,文档会这样说:

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

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

    【讨论】:

    • 我喜欢这个答案,因为它简洁且受文档支持。您能否添加指向您引用的文档的链接。
    • 这甚至可能是一件好事,例如如果您在资源加载时显示加载状态,然后在加载完成时显示内容。
    【解决方案5】:

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

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

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

    【讨论】:

      【解决方案6】:

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

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

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

      【讨论】:

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

      2020 年 4 月更新: 该问题似乎已在最新的 React 16.13.1 中得到修复,请参阅 this sandbox example。感谢@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 我认为这是出于敬意……尽管他们可能会改进它。我没有对此提出任何问题...
      • 似乎不再是这种情况了,至少在 React 16.13.1 的测试中如此:codesandbox.io/s/bold-ellis-n1cid?file=/src/App.js
      • 我们将 React Native 0.63.4 与 React 16.13.1 一起使用,但我们仍然存在未处理的 Promise 拒绝未被错误边界捕获的问题。
      • 实际上,@abernier,如果您关闭代码框中的错误窗口,您会看到页面仍然呈现。我forked your codesandbox 并添加了错误边界。如果您从componentDidMount 中删除async,您将看到错误边界捕获错误。如果你把它留在里面,就不会发现任何错误。只需确保关闭错误消息窗口即可。
      【解决方案8】:

      我喜欢用这样的东西

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

      【讨论】:

        猜你喜欢
        • 2020-02-12
        • 1970-01-01
        • 2020-01-20
        • 2016-11-13
        • 2018-12-23
        • 2020-03-15
        • 1970-01-01
        • 2019-01-22
        • 2019-11-28
        相关资源
        最近更新 更多