Чи добре використовувати async компонентDidMount ()?


139

Чи використання componentDidMount()як функції асинхронізації належної практики в React Native чи я повинен уникати цього?

Мені потрібно отримати деяку інформацію від того, AsyncStorageколи компонент монтується, але єдиний спосіб, який я знаю, зробити це можливим - це зробити componentDidMount()функцію асинхронізації.

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

Чи є проблема з цим і чи є інші рішення цієї проблеми?


2
"Гарна практика" - це питання думки. Це працює? так.
Крайлог

2
Ось хороша стаття, яка показує, чому асинхронізація чекати - це хороший варіант щодо обіцянок hackernoon.com/…
Shubham

просто скористайтеся функцією redux-thunk, це вирішить проблему
Tilak Maddy,

@TilakMaddy Чому ви вважаєте, що кожен реагуючий додаток використовує скорочення?
Міракурун

@Mirakurun, чому весь стек переповнював припущення, що я використовую jQuery, коли я часто задавав питання про JavaScript раніше?
Тілак Меді

Відповіді:


162

Почнемо з вказівки на відмінності та визначення того, як це може спричинити неприємності.

Ось код 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), typecript виправить вам помилку.
    • Дозволяє використовувати 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()методом. Я великий прихильник цього, і до цих пір я не стикався з жодними проблемами, які б дали мені занадто великий головний біль.


Коли ви говорите про проблему, коли перед тимчасовим обіцянкою стався інший setState, чи не те ж саме з Promise без синтаксичного цукру async / очікування чи навіть класичних зворотних викликів?
Clafou

3
Так! Затримка setState()завжди представляє невеликий ризик. Слід ставитися обережно.
Cù Đức Hiếu

Я думаю, один із способів уникнути проблем - використовувати щось на зразок isFetching: trueусередині стану компонента. Я використовував це лише з редукцією, але я вважаю, що це повністю справедливо для управління державою, що реагує лише на реакцію Хоча це насправді не вирішує проблему того ж стану, що оновлюється десь ще в коді ...
Clafou

1
Я з цим згоден. Насправді, isFetchingрішення прапора є досить поширеним, особливо коли ми хочемо відтворити деякі анімації в передній частині, очікуючи на зворотну відповідь ( isFetching: true).
Cù Đức Hiếu

3
Ви можете зіткнутися з проблемами, якщо встановитиState після того, як компонент відключений
Eliezer Steinbock,

18

Оновлення квітня 2020 року: проблема, здається, виправлена ​​в останньому React 16.13.1, дивіться цей приклад пісочниці . Дякуємо @abernier за вказівку на це.


Я провів кілька досліджень, і я виявив одну важливу відмінність: React не обробляє помилки методів життєвого циклу async.

Отже, якщо ви пишете щось подібне:

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. Якщо стан вказує на помилку, відкиньте її з 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>;
  }
}

чи існує повідомлення про це? Може бути корисно повідомити про це, якщо все-таки справа ... thx
abernier

@abernier Я думаю, що це задумано ... Хоча, ймовірно, вони могли б це покращити. Я не подавав жодних питань з цього приводу ...
CF

1
Схоже, це вже не так, принаймні з React 16.13.1, як перевірено тут: codeandbox.io/s/bold-ellis-n1cid?file=/src/App.js
abernier

9

Ваш код чудовий і дуже читабельний для мене. Дивіться цю статтю Дейла Джефферсона, де він показує приклад асинхронізації componentDidMountі також добре виглядає.

Але деякі люди скажуть, що людина, що читає код, може припустити, що React робить щось із поверненою обіцянкою.

Тож тлумачення цього кодексу, і якщо це хороша практика чи ні, є дуже особистим.

Якщо ви хочете іншого рішення, ви можете використовувати обіцянки . Наприклад:

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

3
... або також просто використовувати вбудовану asyncфункцію з awaits всередині ...?
Ерік Каплун

також варіант @ErikAllik :)
Тіаго Алвеш

@ErikAllik у вас трапляється приклад?
Пабло Рінкон

1
@PabloRincon начебто (async () => { const data = await fetch('foo'); const result = await submitRequest({data}); console.log(result) })()де fetchі submitRequestє функціями, які повертають обіцянки.
Ерік Каплун

Цей код, безумовно, поганий, оскільки він проковтне будь-яку помилку у функції getAuth. І якщо функція щось робить із мережею (наприклад), потрібно очікувати помилок.
CF

6

Коли ви використовуєте componentDidMountбез asyncключового слова, документ скаже це:

Ви можете зателефонувати setState () негайно у компонентDidMount (). Це призведе до додаткової візуалізації, але це станеться до того, як браузер оновить екран.

Якщо ви користуєтесь, async componentDidMountви втратите цю здатність: чергове візуалізація відбудеться ПІСЛЯ екрана оновлення браузера. Але imo, якщо ви думаєте про використання асинхронізації, наприклад отримання даних, ви не можете уникнути, коли браузер оновлює екран двічі. В іншому світі неможливо зробити PAUSE компонентDidMount перед екраном оновлення браузера


1
Мені подобається ця відповідь, оскільки вона стисла і підтримується документами. Чи можете ви додати посилання на документи, на які ви посилаєтесь.
thetherSide

Це може бути навіть хорошою справою, наприклад, якщо ви показуєте стан завантаження під час завантаження ресурсу, а потім вміст, коли це зроблено.
Hjulle

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

3

Я думаю, що це добре, поки ти знаєш, що робиш. Але це може бути заплутано, оскільки він async componentDidMount()все ще може працювати після componentWillUnmountзапуску, а компонент відключений.

Ви також можете запустити як синхронні, так і асинхронні завдання всередині componentDidMount. Якби componentDidMountасинхронізація, вам доведеться поставити весь синхронний код перед першим await. Комусь може бути не очевидно, що код до першого awaitзапускається синхронно. У цьому випадку я б, ймовірно, зберігав componentDidMountсинхронний характер, але мав би це методи синхронізації та асинхронного виклику.

Незалежно від того, чи ви вибираєте методи async componentDidMount()синхронізації componentDidMount()викликів проти синхронізації async, ви повинні переконатися, що очищаєте будь-які слухачі чи методи асинхронізації, які все ще можуть працювати, коли компонент відключений.


2

Насправді завантаження асинхронізації в ComponentDidMount є рекомендованою схемою дизайну, оскільки React відходить від застарілих методів життєвого циклу (компонентWillMount, komponentWillReceiveProps, компонентWillUpdate) та надається до Async візуалізації.

Ця публікація в блозі дуже корисна для пояснення, чому це безпечно, і наводить приклади для завантаження асинхронізації в ComponentDidMount:

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


3
Асинхронна візуалізація насправді не має нічого спільного з тим, щоб зробити життєвий цикл явно асинхронним. Це насправді анти-модель. Рекомендоване рішення - насправді викликати метод асинхронізації з методу життєвого циклу
Клейтон Рей

1

Мені подобається використовувати щось подібне

componentDidMount(){
   const result = makeResquest()
}
async makeRequest(){
   const res = await fetch(url);
   const data = await res.json();
   return data
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.