Является ли использование 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 */
}

Глядя на код, я могу указать на следующие различия:

  1. Ключевые слова async: в машинописном тексте это просто маркер кода. Это делает 2 вещи:
    • Принудительное возвращение типа возврата Promise<void> вместо void. Если вы явно укажете, что возвращаемый тип не обещает (например, void), машинописный текст выдаст вам ошибку.
    • Позволяют вам использовать ключевые слова 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();
    }
    

Теперь, как они могли вызвать проблемы?

  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 будет обновлен снова, на этот раз со старыми пользователями и старыми вопросами. Результат также будет виден пользователям.

=> Довольно безопасно (я не уверен примерно на 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(()=>
    {
        //...
    });
}

Если мы все еще хотим, чтобы наша ошибка обогащала границу ошибки, я могу подумать о следующем приеме:

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