使用异步componentDidMount()好吗?

时间:2017-12-25 16:19:41

标签: reactjs asynchronous react-native

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

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

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

是否有任何问题,是否有其他解决方案可以解决这个问题?

8 个答案:

答案 0 :(得分:77)

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

以下是异步和“同步”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),则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();
    }
    
  4. 现在,它们怎么会引起麻烦?

    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,那个时间用旧用户和旧问题。用户也可以看到结果。

    4. =&GT;使用async方法componentDidMount()非常安全(我不确定100%)。我是它的忠实粉丝,到目前为止我还没有遇到任何令我头疼的问题。

答案 1 :(得分:6)

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

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

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

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

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

答案 2 :(得分:2)

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

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

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

答案 3 :(得分:1)

更新:

(我的构建: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 :(得分:1)

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

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

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

答案 5 :(得分:1)

我喜欢用这样的东西

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

答案 6 :(得分:0)

当您使用不带componentDidMount关键字的async时,医生会这样说:

  

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

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

答案 7 :(得分:0)

我做了一些研究,发现了一个重要的区别: 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 ...

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

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