使用Async ComponentDidMount()好吗?

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

当组件挂载时,我需要从AsyncStorage获得一些信息,但我所知道的唯一方法是使componentDidMount()功能异步。

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

这有什么问题吗?这个问题还有其他解决办法吗?

126759 次浏览

你的代码很好,对我来说可读性很强。看这个戴尔·杰斐逊的文章,他展示了一个异步componentDidMount的例子,看起来也很不错。

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

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

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

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

让我们从指出差异并确定它如何引起麻烦开始。

下面是Async和“ Sync ”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。如果您显式地将返回类型指定为非承诺(例如: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 async keyword 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 await keyword 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.

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

更新:

(我的版本: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);
}
}

您将遇到以下错误..

未捕获引用错误:未定义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));
}

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

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

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

我觉得只要你知道自己在做什么就好。但它可能会造成混淆,因为在componentWillUnmount运行且组件卸载后,async componentDidMount()可能仍在运行。

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

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

实际上,ComponentDidMount中的异步加载是一个推荐的设计模式,因为React从遗留的生命周期方法(ComponentWillMount、ComponentWillReceiveProps、Component WillUpdate)转移到了异步呈现。

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

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

2020年4月更新: 此问题似乎已在最新的REACT 16.1 3.1中修复,请参阅这个沙盒的例子。感谢@Abernier指出这一点。


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

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

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

然后,您的错误将被误差边界捕获,您可以处理它并显示一条优雅的消息。

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

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

相当于:

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

然后你的错误将被默默地吞下。。为你感到羞耻,反应..

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

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

或者像这样:

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

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

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

示例:

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

我喜欢用这样的东西。

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

为了标记@C-F的答案,我添加了一个TypeScript装饰器 (AsyncMethodErrorHandler)来处理async componentDidMount()中的错误和其他无法将错误冒泡到 应用程序状态.

我发现这比在应用程序的try/catch块中包装几十个异步方法更容易。 我继承了他的遗产。

class BuggyComponent extends React.Component<{error_message?:string}> {


@AsyncMethodErrorHandler("error_message")
async componentDidMount() {
await things_that_might_fail();
}


render(){
if(this.state.error_message){
return <p>Something went wrong: {this.state.error_message}</p>
}
}
}




function AsyncMethodErrorHandler(
/* Key in the this.state to store error messages*/
key: string,
/* function for transforming the error into the value stored in this.state[key] */
error_handler: string | { (e: Error): string } = (e: Error) => e.message
) {


return function (
cls: React.Component,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const f: { (...args: any[]): Promise<any> } = descriptor.value;
return {
...descriptor,
value: function (...args: any[]) {
return f.apply(this, args).catch((e: Error) => {
console.log(`an error occured in the ${propertyKey} Method:`, e);
(this as any as React.Component).setState({
[key]:
typeof error_handler === "string"
? error_handler
: error_handler(e),
});
});
},
};
};
}

请注意,在撰写本文时,此解决方案不适用于abc0__异步函数属性:

属性修饰符只能用于观察 已为类

声明了特定名称