Является ли использование async componentDidMount() хорошим?
Использует componentDidMount()
как эффективную практику асинхронной функции в React Native или я должен ее избегать?
Мне нужно получить некоторую информацию из AsyncStorage
, когда компонент монтируется, но единственный способ, которым я знаю, сделать это возможно, - это сделать функцию componentDidMount()
async.
async componentDidMount() {
let auth = await this.getAuth();
if (auth)
this.checkAuth(auth);
}
Есть ли проблемы с этим и есть ли другие решения этой проблемы?
Ответы
Ответ 1
Начнем с указания на различия и определения того, как это может вызвать проблемы.
Ниже приведен код метода жизненного цикла async и "sync" componentDidMount()
:
// This is typescript code
componentDidMount(): void { /* do something */ }
async componentDidMount(): Promise<void> {
/* do something */
/* You can use "await" here */
}
Глядя на код, я могу указать на следующие различия:
- Ключевые слова
async
: в машинописном тексте это просто маркер кода. Это делает 2 вещи: - Принудительное возвращение типа возврата
Promise<void>
вместо void
. Если вы явно укажете, что возвращаемый тип не обещает (например, void), машинописный текст выдаст вам ошибку. - Позволяют вам использовать ключевые слова
await
внутри метода.
- Тип возврата изменяется с
void
на Promise<void>
- Это означает, что теперь вы можете сделать это:
async someMethod(): Promise<void> { await componentDidMount(); }
-
Теперь вы можете использовать ключевое слово 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();
}
Теперь, как они могли вызвать проблемы?
- Ключевое слово
async
абсолютно безвредно. -
Я не могу представить себе ситуацию, в которой вам нужно вызвать метод componentDidMount()
чтобы тип возврата Promise<void>
был безвредным.
Вызов метода с возвращаемым типом Promise<void>
без ключевого слова await
будет иметь никакого значения от вызова метода с возвращаемым типом void
.
-
Поскольку нет никаких методов жизненного цикла после componentDidMount()
задержка его выполнения кажется довольно безопасной. Но есть гоча.
Допустим, вышеупомянутый this.setState({users, questions});
будет выполнен через 10 секунд. В середине задержки, еще один...
this.setState({users: newerUsers, questions: newerQuestions});
... были успешно выполнены и DOM были обновлены. Результат был виден пользователям. Часы продолжали тикать, и прошло 10 секунд. Отсроченное this.setState(...)
будет затем выполнено, и DOM будет обновлен снова, на этот раз со старыми пользователями и старыми вопросами. Результат также будет виден пользователям.
=> Довольно безопасно (я не уверен примерно на 100%) использовать async
с методом componentDidMount()
. Я большой поклонник этого, и до сих пор я не сталкивался с какими-либо проблемами, которые причиняют мне слишком много головной боли.
Ответ 2
Ваш код в порядке и очень читаем для меня. См. Эту статью Дейла Джефферсона, где он показывает пример async componentDidMount
и выглядит действительно хорошо.
Но некоторые люди скажут, что человек, читающий код, может предположить, что React что-то делает с возвращенным обещанием.
Поэтому интерпретация этого кода и, если это хорошая практика или нет, очень личная.
Если вы хотите другое решение, вы можете использовать обещания. Например:
componentDidMount() {
fetch(this.getAuth())
.then(auth => {
if (auth) this.checkAuth(auth)
})
}
Ответ 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);
}
}
вы столкнетесь с следующей ошибкой...
Uncaught ReferenceError: регенераторRuntime не определен
В этом случае вам нужно будет установить 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
Я думаю, это прекрасно, если вы знаете, что делаете. Но это может сбивать с толку, поскольку async componentDidMount()
все еще может работать после запуска componentWillUnmount
, и компонент размонтирован.
Вы также можете запустить как синхронные, так и асинхронные задачи внутри componentDidMount
. Если componentDidMount
был асинхронным, вам пришлось бы поставить весь синхронный код до первого await
. Для кого-то может не показаться очевидным, что код перед первым await
выполняется синхронно. В этом случае я бы, вероятно, продолжал синхронный componentDidMount
но он вызывал синхронные и асинхронные методы.
Независимо от того, выбираете ли вы async componentDidMount()
и sync componentDidMount()
вызова методов async
, вы должны убедиться, что вы очищаете любые слушатели или методы асинхронизации, которые все еще могут выполняться, когда компонент размонтируется.
Ответ 5
На самом деле асинхронная загрузка в ComponentDidMount является рекомендуемым шаблоном проектирования, поскольку React отходит от устаревших методов жизненного цикла (componentWillMount, componentWillReceiveProps, componentWillUpdate) и переходит к асинхронному рендерингу.
Эта запись блога очень полезна для объяснения того, почему это безопасно, и предоставления примеров асинхронной загрузки в ComponentDidMount:
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html
Ответ 6
Когда вы используете componentDidMount
без async
ключевого слова, документ говорит об этом:
Вы можете сразу же вызвать setState() в componentDidMount(). Это вызовет дополнительный рендеринг, но это произойдет до того, как браузер обновит экран.
Если вы используете async componentDidMount
вы потеряете эту способность: произойдет другой рендеринг ПОСЛЕ обновления браузера на экране. Но imo, если вы думаете об использовании async, например, об извлечении данных, вы не можете обойтись, браузер дважды обновит экран. В другом мире невозможно PAUSE componentDidMount, прежде чем браузер обновит экран
Ответ 7
Я провел некоторое исследование и обнаружил одно важное отличие: React не обрабатывает ошибки из асинхронных методов жизненного цикла.
Итак, если вы напишите что-то вроде этого:
componentDidMount()
{
throw new Error('I crashed!');
}
тогда ваша ошибка будет перехвачена границей ошибки, и вы сможете обработать ее и отобразить изящное сообщение.
Если мы изменим код следующим образом:
async componentDidMount()
{
throw new Error('I crashed!');
}
что эквивалентно этому:
componentDidMount()
{
return Promise.reject(new Error('I crashed!'));
}
тогда ваша ошибка будет тихо проглочена. Позор тебе, Реакт...
Итак, как мы обрабатываем ошибки, чем? Единственный способ, по-видимому, - явный улов вроде этого:
componentDidMount()
{
try
{
await myAsyncFunction();
}
catch(error)
{
//...
}
}
или вот так:
componentDidMount()
{
myAsyncFunction()
.catch(()=>
{
//...
});
}
Если мы все еще хотим, чтобы наша ошибка обогащала границу ошибки, я могу подумать о следующем приеме:
- Поймай ошибку, заставь обработчик ошибок изменить состояние компонента
- Если состояние указывает на ошибку, выведите ее из метода
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>;
}
}