Я пробовал пример 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



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Когда вы используете асинхронную функцию, например
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();
}, []);
второе решение упростит чтение и поможет вам написать код для отмены предыдущих запросов, если запущен новый, или сохранить последний ответ на запрос в состоянии
но eslint не потерпит с этим
нет возможности выполнить обратный вызов cleanup / didmount
@DavidRearte, я не понял, что вы пытаетесь сказать
@ShubhamKhatri, когда вы используете useEffect, вы можете вернуть функцию для очистки, например, для отмены подписки на события. Когда вы используете асинхронную функцию, вы ничего не можете вернуть, потому что useEffect не дождется результата
@DavidRearte да, useEffect не будет ждать результата перед очисткой, и, следовательно, при очистке вы должны отменить асинхронный запрос
Вы говорите, что я могу добавить функцию очистки в асинхронную? Я пробовал, но моя функция очистки никогда не вызывается. Вы можете привести небольшой пример?
Предлагаю посмотреть Дэн Абрамов (один из разработчиков ядра 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 } }, [])
Вы также можете использовать пакет с именем использовать-асинхронный-эффект. Этот пакет позволяет использовать синтаксис async await.
Использование функции самовызова, не позволяющей асинхронному процессу просачиваться в определение функции useEffect, или пользовательская реализация функции, которая запускает асинхронный вызов в качестве оболочки вокруг useEffect, на данный момент является лучшим вариантом. Хотя вы можете включить новый пакет, такой как предлагаемый use-async-effect, я думаю, что это простая проблема.
эй, это нормально, чем я занимаюсь чаще всего. но eslint просит меня сделать fetchMyAPI() зависимым от useEffect
Пока 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, более заметным, а поведение кода в результате, будем надеяться, более осознанным и правильным.
Я прочитал этот вопрос и считаю, что лучший способ реализовать 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())?
Да, это так, но я думаю, он опускал это для простоты.
пытаться
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 не устраняет такое поведение. Я ценю твой ответ.
На всякий случай люди задаются вопросом, почему это правда ... Без фигурных скобок возвращаемое значение initData () неявно возвращается стрелочной функцией. С фигурными скобками ничего не возвращается неявно, поэтому ошибки не произойдет.
Здесь можно использовать недействительный оператор. Вместо:
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 - это новая штука, которая доступна после того, как предложение об отмене обещаний не удалось.
Кстати, есть пакет "use-abortable-fetch", но я не уверен, что мне нравится, как он был написан. Было бы неплохо получить простую версию этого кода, которая у вас здесь, в качестве настраиваемого хука. Кроме того, «await-here» - довольно хороший пакет, который может избавить от необходимости в блоке try / catch.
Я предпочитаю React.useEffect(() => { (async () => () {... })();}, []); еще короче
Пожалуйста, попробуйте это
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.
Я думаю, что важно отметить асинхронные эффекты, что вы должны обрабатывать ситуации, когда компонент отключается после запуска эффекта, но до выполнения обратного вызова. Предполагая, что вышеупомянутый fetch занимает 500 мс, а компонент отключается через 250 мс, обратный вызов попытается обновить состояние на отключенном компоненте, выдав ошибку.
С хуком 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;
Оберните его в 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 в своем примере, поэтому это не будет работать должным образом.
Вам не нужно ждать getPosts? Изучите перед тем, как публиковать ложный комментарий
Вы также можете использовать формат 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
...
проверь как useAff убивает свой Aff в функции cleanup
Aff реализован как состояние машины (без обещаний)
но для нас важно то, что:
Aff кодирует, как остановить Aff - Здесь вы можете поместить свой AbortControllerEffect (не тестировался) и 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>);
}
Был создан пакет, упрощающий эту задачу. Вы можете найти его здесь.