¿Usar componentDidMount() como una buena práctica de función asíncrona en React Native o debería evitarlo?
Necesito obtener información de AsyncStorage cuando se monta el componente, pero la única forma que conozco de hacerlo posible es hacer que la función componentDidMount() sea asíncrona.
async componentDidMount() { let auth = await this.getAuth(); if (auth) this.checkAuth(auth); }¿Hay algún problema con eso y hay otras soluciones a este problema?
Comencemos señalando las diferencias y determinando cómo podría causar problemas.
Aquí está el código del método de ciclo de vida asíncrono y "sincronizado" componentDidMount() :
// This is typescript code componentDidMount(): void { /* do something */ } async componentDidMount(): Promise<void> { /* do something */ /* You can use "await" here */ }Mirando el código, puedo señalar las siguientes diferencias:
async : en texto mecanografiado, esto es simplemente un marcador de código. Hace 2 cosas:Promise<void> en lugar de void . Si especifica explícitamente que el tipo de retorno no es una promesa (por ejemplo, nulo), TypeScript le arrojará un error.await dentro del método.void a Promise<void>async someMethod(): Promise<void> { await componentDidMount(); } Ahora puede usar la palabra clave await dentro del método y pausar temporalmente su ejecución. Me gusta esto:
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(); }Ahora, ¿cómo podrían causar problemas?
async es absolutamente inofensiva. No puedo imaginar ninguna situación en la que necesite realizar una llamada al método componentDidMount() para que el tipo de retorno Promise<void> también sea inofensivo.
Llamar a un método que tiene el tipo de retorno Promise<void> sin la palabra clave await no hará ninguna diferencia con respecto a llamar a uno que tiene el tipo de retorno void .
Dado que no hay métodos de ciclo de vida después de que componentDidMount() retrase su ejecución parece bastante seguro. Pero hay un problema.
Digamos, lo anterior this.setState({users, questions}); se ejecutaría después de 10 segundos. En medio del tiempo de retraso, otro...
this.setState({users: newerUsers, questions: newerQuestions});
... se ejecutaron con éxito y se actualizó el DOM. El resultado era visible para los usuarios. El reloj siguió corriendo y pasaron 10 segundos. El retraso this.setState(...) luego se ejecutaría y el DOM se actualizaría nuevamente, esa vez con usuarios antiguos y preguntas antiguas. El resultado también sería visible para los usuarios.
=> Es bastante seguro (no estoy seguro al 100%) usar async con el método componentDidMount() . Soy un gran admirador y hasta ahora no he encontrado ningún problema que me dé mucho dolor de cabeza.
Actualización de abril de 2020: el problema parece haberse solucionado en la última versión de React 16.13.1, vea este ejemplo de sandbox . Gracias a @abernier por señalar esto.
Investigué un poco y encontré una diferencia importante: React no procesa errores de métodos de ciclo de vida asíncronos.
Entonces, si escribes algo como esto:
componentDidMount() { throw new Error('I crashed!'); }entonces su error será capturado por el límite de error , y puede procesarlo y mostrar un mensaje elegante.
Si cambiamos el código así:
async componentDidMount() { throw new Error('I crashed!'); }que es equivalente a esto:
componentDidMount() { return Promise.reject(new Error('I crashed!')); }entonces su error será tragado silenciosamente . Qué vergüenza, reacciona...
Entonces, ¿cómo procesamos los errores que? La única forma parece ser una captura explícita como esta:
async componentDidMount() { try { await myAsyncFunction(); } catch(error) { //... } }o así:
componentDidMount() { myAsyncFunction() .catch(()=> { //... }); }Si todavía queremos que nuestro error alcance el límite de error, puedo pensar en el siguiente truco:
renderEjemplo:
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>; } }Tu código está bien y es muy legible para mí. Vea este artículo de Dale Jefferson donde muestra un ejemplo de componentDidMount asíncronoDidMount y también se ve muy bien.
Pero algunas personas dirían que una persona que lee el código puede asumir que React hace algo con la promesa devuelta.
Entonces la interpretación de este código y si es una buena práctica o no es muy personal.
Si desea otra solución, puede usar las promesas . Por ejemplo:
componentDidMount() { fetch(this.getAuth()) .then(auth => { if (auth) this.checkAuth(auth) }) }