Предупреждения React Hook для асинхронной функции в useEffect: функция useEffect должна возвращать функцию очистки или ничего

Я пробовал пример useEffect примерно так:

useEffect(async () => {
    try {
        const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
        const json = await response.json();
        setPosts(json.data.children.map(it => it.data));
    } catch (e) {
        console.error(e);
    }
}, []);

и я получаю это предупреждение на своей консоли. Но я думаю, что очистка необязательна для асинхронных вызовов. Я не знаю, почему я получаю это предупреждение. Связывание песочницы для примеров. https://codesandbox.io/s/24rj871r0pПредупреждения React Hook для асинхронной функции в useEffect: функция useEffect должна возвращать функцию очистки или ничего

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
344
0
392 568
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

Когда вы используете асинхронную функцию, например

async () => {
    try {
        const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
        const json = await response.json();
        setPosts(json.data.children.map(it => it.data));
    } catch (e) {
        console.error(e);
    }
}

он возвращает обещание, а useEffect не ожидает, что функция обратного вызова вернет обещание, скорее он ожидает, что ничего не будет возвращено или функция будет возвращена.

В качестве обходного пути для предупреждения вы можете использовать самозапускающуюся асинхронную функцию.

useEffect(() => {
    (async function() {
        try {
            const response = await fetch(
                `https://www.reddit.com/r/${subreddit}.json`
            );
            const json = await response.json();
            setPosts(json.data.children.map(it => it.data));
        } catch (e) {
            console.error(e);
        }
    })();
}, []);

или, чтобы сделать его более чистым, вы можете определить функцию, а затем вызвать ее

useEffect(() => {
    async function fetchData() {
        try {
            const response = await fetch(
                `https://www.reddit.com/r/${subreddit}.json`
            );
            const json = await response.json();
            setPosts(json.data.children.map(it => it.data));
        } catch (e) {
            console.error(e);
        }
    };
    fetchData();
}, []);

второе решение упростит чтение и поможет вам написать код для отмены предыдущих запросов, если запущен новый, или сохранить последний ответ на запрос в состоянии

Рабочие коды

Был создан пакет, упрощающий эту задачу. Вы можете найти его здесь.

KittyCat 27.11.2018 13:12

но eslint не потерпит с этим

Muhaimin CS 10.01.2019 08:02

нет возможности выполнить обратный вызов cleanup / didmount

David Rearte 09.04.2019 05:01

@DavidRearte, я не понял, что вы пытаетесь сказать

Shubham Khatri 09.04.2019 07:18

@ShubhamKhatri, когда вы используете useEffect, вы можете вернуть функцию для очистки, например, для отмены подписки на события. Когда вы используете асинхронную функцию, вы ничего не можете вернуть, потому что useEffect не дождется результата

David Rearte 10.04.2019 21:48

@DavidRearte да, useEffect не будет ждать результата перед очисткой, и, следовательно, при очистке вы должны отменить асинхронный запрос

Shubham Khatri 10.04.2019 21:54

Вы говорите, что я могу добавить функцию очистки в асинхронную? Я пробовал, но моя функция очистки никогда не вызывается. Вы можете привести небольшой пример?

David Rearte 10.04.2019 22:06
Ответ принят как подходящий

Предлагаю посмотреть Дэн Абрамов (один из разработчиков ядра React) отвечает здесь:

I think you're making it more complicated than it needs to be.

function Example() {
  const [data, dataSet] = useState<any>(null)

  useEffect(() => {
    async function fetchMyAPI() {
      let response = await fetch('api/data')
      response = await response.json()
      dataSet(response)
    }

    fetchMyAPI()
  }, [])

  return <div>{JSON.stringify(data)}</div>
}

Longer term we'll discourage this pattern because it encourages race conditions. Such as — anything could happen between your call starts and ends, and you could have gotten new props. Instead, we'll recommend Suspense for data fetching which will look more like

const response = MyAPIResource.read();

and no effects. But in the meantime you can move the async stuff to a separate function and call it.

Вы можете узнать больше о экспериментальное ожидание здесь.


Если вы хотите использовать внешние функции с eslint.

 function OutsideUsageExample({ userId }) {
  const [data, dataSet] = useState<any>(null)

  const fetchMyAPI = useCallback(async () => {
    let response = await fetch('api/data/' + userId)
    response = await response.json()
    dataSet(response)
  }, [userId]) // if userId changes, useEffect will run again
               // if you want to run only once, just leave array empty []

  useEffect(() => {
    fetchMyAPI()
  }, [fetchMyAPI])

  return (
    <div>
      <div>data: {JSON.stringify(data)}</div>
      <div>
        <button onClick = {fetchMyAPI}>manual fetch</button>
      </div>
    </div>
  )
}

Если вы будете использовать useCallback, посмотрите, как это работает useCallback. Песочница.

import React, { useState, useEffect, useCallback } from "react";

export default function App() {
  const [counter, setCounter] = useState(1);

  // if counter is changed, than fn will be updated with new counter value
  const fn = useCallback(() => {
    setCounter(counter + 1);
  }, [counter]);

  // if counter is changed, than fn will not be updated and counter will be always 1 inside fn
  /*const fnBad = useCallback(() => {
      setCounter(counter + 1);
    }, []);*/

  // if fn or counter is changed, than useEffect will rerun
  useEffect(() => {
    if (!(counter % 2)) return; // this will stop the loop if counter is not even

    fn();
  }, [fn, counter]);

  // this will be infinite loop because fn is always changing with new counter value
  /*useEffect(() => {
    fn();
  }, [fn]);*/

  return (
    <div>
      <div>Counter is {counter}</div>
      <button onClick = {fn}>add +1 count</button>
    </div>
  );
}

Вы можете решить проблемы, связанные с состоянием гонки, проверив, отключен ли компонент следующим образом: useEffect(() => { let unmounted = false promise.then(res => { if (!unmounted) { setState(...) } }) return () => { unmounted = true } }, [])

Richard 28.02.2019 12:06

Вы также можете использовать пакет с именем использовать-асинхронный-эффект. Этот пакет позволяет использовать синтаксис async await.

KittyCat 01.05.2019 14:04

Использование функции самовызова, не позволяющей асинхронному процессу просачиваться в определение функции useEffect, или пользовательская реализация функции, которая запускает асинхронный вызов в качестве оболочки вокруг useEffect, на данный момент является лучшим вариантом. Хотя вы можете включить новый пакет, такой как предлагаемый use-async-effect, я думаю, что это простая проблема.

Thulani Chivandikwa 04.06.2019 23:22

эй, это нормально, чем я занимаюсь чаще всего. но eslint просит меня сделать fetchMyAPI() зависимым от useEffect

Prakash Reddy Potlapadu 16.03.2020 15:16

Пока React не предоставит лучший способ, вы можете создать помощник useEffectAsync.js:

import { useEffect } from 'react';


export default function useEffectAsync(effect, inputs) {
    useEffect(() => {
        effect();
    }, inputs);
}

Теперь вы можете передать асинхронную функцию:

useEffectAsync(async () => {
    const items = await fetchSomeItems();
    console.info(items);
}, []);

Обновлять

Если вы выберете такой подход, обратите внимание, что это дурной тон. Я прибегаю к этому, когда знаю, что это безопасно, но это всегда дурной тон и случайность.

Приостановка получения данных, который все еще является экспериментальным, решит некоторые из случаев.

В других случаях вы можете моделировать асинхронные результаты как события, чтобы вы могли добавлять или удалять слушателя в зависимости от жизненного цикла компонента.

Или вы можете смоделировать асинхронные результаты как Наблюдаемый, чтобы вы могли подписываться и отказываться от подписки в зависимости от жизненного цикла компонента.

Причина, по которой React не разрешает автоматически использовать асинхронные функции в useEffect, заключается в том, что в большинстве случаев требуется некоторая очистка. Функция useAsyncEffect в том виде, в каком вы ее написали, может легко ввести кого-то в заблуждение, если он вернет функцию очистки из своего асинхронного эффекта, и она будет запущена в подходящее время. Это могло привести к утечкам памяти или еще более серьезным ошибкам, поэтому мы решили побудить людей провести рефакторинг своего кода, чтобы сделать «шов» асинхронных функций, взаимодействующих с жизненным циклом React, более заметным, а поведение кода в результате, будем надеяться, более осознанным и правильным.

Sophie Alpert 01.01.2020 10:03

Я прочитал этот вопрос и считаю, что лучший способ реализовать useEffect не упоминается в ответах. Допустим, у вас есть сетевой вызов, и вы хотите что-то сделать, когда получите ответ. Для простоты сохраним ответ сети в переменной состояния. Кто-то может захотеть использовать действие / редуктор для обновления магазина с помощью сетевого ответа.

const [data, setData] = useState(null);

/* This would be called on initial page load */
useEffect(()=>{
    fetch(`https://www.reddit.com/r/${subreddit}.json`)
    .then(data => {
        setData(data);
    })
    .catch(err => {
        /* perform error handling if desired */
    });
}, [])

/* This would be called when store/state data is updated */
useEffect(()=>{
    if (data) {
        setPosts(data.children.map(it => {
            /* do what you want */
        }));
    }
}, [data]);

Ссылка => https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

не правда ли, вам тоже нужна линейка then(res => res.json())?

nonopolarity 07.02.2021 21:27

Да, это так, но я думаю, он опускал это для простоты.

Anthony Luzquiños 06.03.2021 18:30

пытаться

const MyFunctionnalComponent: React.FC = props => {
  useEffect(() => {
    // Using an IIFE
    (async function anyNameFunction() {
      await loadContent();
    })();
  }, []);
  return <div></div>;
};

Для других читателей ошибка может возникать из-за отсутствия скобок, заключающих асинхронную функцию:

Учитывая асинхронную функцию initData

  async function initData() {
  }

Этот код приведет к вашей ошибке:

  useEffect(() => initData(), []);

Но этот не будет:

  useEffect(() => { initData(); }, []);

(Обратите внимание на скобки вокруг initData ()

Молодец, чувак! Я использую сагу, и эта ошибка появилась, когда я вызвал создателя действия, который вернул единственный объект. Похоже, что callback-функция useEffect не устраняет такое поведение. Я ценю твой ответ.

Gorr1995 26.03.2020 11:28

На всякий случай люди задаются вопросом, почему это правда ... Без фигурных скобок возвращаемое значение initData () неявно возвращается стрелочной функцией. С фигурными скобками ничего не возвращается неявно, поэтому ошибки не произойдет.

Marnix.hoh 21.04.2020 17:28

Здесь можно использовать недействительный оператор. Вместо:

React.useEffect(() => {
    async function fetchData() {
    }
    fetchData();
}, []);

или

React.useEffect(() => {
    (async function fetchData() {
    })()
}, []);

можно было написать:

React.useEffect(() => {
    void async function fetchData() {
    }();
}, []);

Он немного чище и красивее.


Асинхронные эффекты могут вызвать утечку памяти, поэтому важно выполнить очистку при отключении компонента. В случае выборки это могло бы выглядеть так:

function App() {
    const [ data, setData ] = React.useState([]);

    React.useEffect(() => {
        const abortController = new AbortController();
        void async function fetchData() {
            try {
                const url = 'https://jsonplaceholder.typicode.com/todos/1';
                const response = await fetch(url, { signal: abortController.signal });
                setData(await response.json());
            } catch (error) {
                console.info('error', error);
            }
        }();
        return () => {
            abortController.abort(); // cancel pending fetch request on component unmount
        };
    }, []);

    return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

Вы знаете свой JS, сэр. AbortController - это новая штука, которая доступна после того, как предложение об отмене обещаний не удалось.

Devin Rhode 02.11.2020 16:21

Кстати, есть пакет "use-abortable-fetch", но я не уверен, что мне нравится, как он был написан. Было бы неплохо получить простую версию этого кода, которая у вас здесь, в качестве настраиваемого хука. Кроме того, «await-here» - довольно хороший пакет, который может избавить от необходимости в блоке try / catch.

Devin Rhode 02.11.2020 18:28

Я предпочитаю React.useEffect(() => { (async () => () {... })();}, []); еще короче

Mugen 10.01.2021 18:05

Пожалуйста, попробуйте это

 useEffect(() => {
        (async () => {
          const products = await api.index()
          setFilteredProducts(products)
          setProducts(products)
        })()
      }, [])

Для выборки из внешнего API с использованием React Hooks вы должны вызвать функцию, которая извлекает из API внутри ловушки useEffect.

Нравится:

async function fetchData() {
    const res = await fetch("https://swapi.co/api/planets/4/");
    res
      .json()
      .then(res => setPosts(res))
      .catch(err => setErrors(err));
  }

useEffect(() => {
    fetchData();
}, []);

Я настоятельно рекомендую вам не определять свой запрос внутри ловушки useEffect, потому что он будет повторно визуализироваться бесконечное количество раз. И поскольку вы не можете сделать useEffect асинхронным, вы можете сделать функцию внутри него асинхронной.

В показанном выше примере вызов API находится в другом разделенная асинхронная функция, поэтому он гарантирует, что вызов является асинхронным и выполняется только один раз. Кроме того, массив зависимостей useEffect's ([]) пуст, что означает, что он будет вести себя так же, как componentDidMount из компонентов класса React, он будет выполняться только один раз при монтировании компонента.

Для текста загрузки вы можете использовать условный рендеринг React, чтобы проверить, являются ли ваши сообщения нулевыми, если они есть, отобразить текст загрузки, иначе отобразить сообщения. Else будет истинным, когда вы завершите выборку данных из API и сообщения не будут нулевыми.

{posts === null ? <p> Loading... </p> 
: posts.map((post) => (
    <Link key = {post._id} to = {`/blog/${post.slug.current}`}>
      <img src = {post.mainImage.asset.url} alt = {post.mainImage.alt} />
      <h2>{post.title}</h2>
   </Link>
))}

Я вижу, что вы уже используете условный рендеринг, поэтому я рекомендую вам углубиться в него, особенно для проверки, является ли объект нулевым или нет!

Я рекомендую вам прочитать следующие статьи, если вам нужна дополнительная информация об использовании API с помощью хуков.

https://betterprogramming.pub/how-to-fetch-data-from-an-api-with-react-hooks-9e7202b8afcd

https://reactjs.org/docs/conditional-rendering.html

Почему вы используете .then с await в одном блоке в определении функции? Я думал, что весь смысл await в замене .then.

gene b. 16.04.2021 17:21

Я думаю, что важно отметить асинхронные эффекты, что вы должны обрабатывать ситуации, когда компонент отключается после запуска эффекта, но до выполнения обратного вызова. Предполагая, что вышеупомянутый fetch занимает 500 мс, а компонент отключается через 250 мс, обратный вызов попытается обновить состояние на отключенном компоненте, выдав ошибку.

Magnus Bull 23.11.2021 10:38

С хуком useAsyncEffect, предоставляемым настраиваемым библиотека, безопасное выполнение асинхронного кода и выполнение запросов внутри эффектов становится тривиальным делом, поскольку он делает ваш код автоматически отменяемым (это только одна вещь из списка функций). Проверьте Живая демонстрация с загрузкой JSON

import React from "react";
import { useAsyncEffect } from "use-async-effect2";
import cpFetch from "cp-fetch";

/*
 Notice: the related network request will also be aborted
 Checkout your network console
 */

function TestComponent(props) {
  const [cancel, done, result, err] = useAsyncEffect(
    function* () {
      const response = yield cpFetch(props.url).timeout(props.timeout);
      return yield response.json();
    },
    { states: true, deps: [props.url] }
  );

  return (
    <div className = "component">
      <div className = "caption">useAsyncEffect demo:</div>
      <div>
        {done ? (err ? err.toString() : JSON.stringify(result)) : "loading..."}
      </div>
      <button className = "btn btn-warning" onClick = {cancel} disabled = {done}>
        Cancel async effect
      </button>
    </div>
  );
}

export default TestComponent;

Та же демонстрация с использованием axios

Оберните его в useCallback и используйте как зависимость от ловушки useEffect.

const getPosts = useCallback(async () => {
    try {
        const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
        const json = await response.json();
        setPosts(json.data.children.map(it => it.data));
    } catch (e) {
        console.error(e);
    }
}, []);

useEffect(async () => {
    getPosts();
}, [getPosts]);

Вы не ждете getPosts () в useEffect в своем примере, поэтому это не будет работать должным образом.

George Pandurov 01.11.2021 10:12

Вам не нужно ждать getPosts? Изучите перед тем, как публиковать ложный комментарий

Tom el Safadi 02.11.2021 03:00

Вы также можете использовать формат IIFE, чтобы не усложнять

function Example() {
    const [data, dataSet] = useState<any>(null)

    useEffect(() => {
        (async () => {
            let response = await fetch('api/data')
            response = await response.json()
            dataSet(response);
        })();
    }, [])

    return <div>{JSON.stringify(data)}</div>
}

Просто заметка о том, КАК УДИВИТЕЛЬНО язык purescript решает эту проблему устаревших эффектов с монадой Aff.

БЕЗ ЧИСТОГО СКРИПТА

вам нужно использовать AbortController

function App() {
    const [ data, setData ] = React.useState([]);

    React.useEffect(() => {
        const abortController = new AbortController();
        void async function fetchData() {
            try {
                const url = 'https://jsonplaceholder.typicode.com/todos/1';
                const response = await fetch(url, { signal: abortController.signal });
                setData(await response.json());
            } catch (error) {
                console.info('error', error);
            }
        }();
        return () => {
            abortController.abort(); // cancel pending fetch request on component unmount
        };
    }, []);

    return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

или stale (из примера NoahZinsmeister / web3-react)

function Balance() {
  const { account, library, chainId } = useWeb3React()

  const [balance, setBalance] = React.useState()
  React.useEffect((): any => {
    if (!!account && !!library) {      
      let stale = false
      
      library 
        .getBalance(account)
        .then((balance: any) => {
          if (!stale) {
            setBalance(balance)
          }
        })
        .catch(() => {
          if (!stale) {
            setBalance(null)
          }
        })

      return () => { // NOTE: will be called every time deps changes
        stale = true
        setBalance(undefined)
      }
    }
  }, [account, library, chainId]) // ensures refresh if referential identity of library doesn't change across chainIds

  ...

С PURESCRIPT

проверь как useAff убивает свой Aff в функции cleanup

Aff реализован как состояние машины (без обещаний)

но для нас важно то, что:

  • Aff кодирует, как остановить Aff - Здесь вы можете поместить свой AbortController
  • он ОСТАНОВИТ запуск Effect (не тестировался) и Aff (он не будет запускать then из второго примера, поэтому НЕ будет setBalance(balance)) ЕСЛИ ошибка была брошен НА волокно ИЛИ ВНУТРИ волокна

В таком случае лучше всего использовать КСВ.

Базовый пример:

import useSWR from 'swr'

function Profile() {
  const { data, error } = useSWR('/api/user', fetcher)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>
  return <div>hello {data.name}!</div>
}

В этом примере ловушка useSWR принимает ключевую строку и функцию извлечения. key - это уникальный идентификатор данных (обычно URL-адрес API), который передается в fetcher. fetcherможет быть любой асинхронной функцией, который возвращает данные, вы можете использовать встроенную выборку или такие инструменты, как Axios.

Итак, сборщик может быть определен с помощью всего:

import fetch from 'unfetch'
const fetcher = url => fetch(url).then(r => r.json())

Хук возвращает 2 значения: данные и ошибку, в зависимости от статуса запроса.

Таким образом, вы также получаете там обработку ошибок и множество других интересных функций, таких как Автоматическая повторная проверка.

Источник: Реагировать на ловушки для извлечения данных

Не обращайте внимания на предупреждение и используйте ловушку useEffect с async function следующим образом:

import { useEffect, useState } from "react";

function MyComponent({ objId }) {
  const [data, setData] = useState();

  useEffect(() => {
    if (objId === null || objId === undefined) {
      return;
    }

    async function retrieveObjectData() {
      const response = await fetch(`path/to/api/objects/${objId}/`);
      const jsonData = response.json();
      setData(jsonData);
    }
    retrieveObjectData();

  }, [objId]);

  if (objId === null || objId === undefined) {
    return (<span>Object ID needs to be set</span>);
  }

  if (data) {
    return (<span>Object ID is {objId}, data is {data}</span>);
  }

  return (<span>Loading...</span>);
}

Другие вопросы по теме