Proponuję spojrzeć na odpowiedź Dana Abramova (jednego z głównych opiekunów Reacta) :
Myślę, że komplikujesz to bardziej, niż powinno.
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>
}
Na dłuższą metę zniechęcamy do tego wzoru, ponieważ sprzyja on warunkom wyścigu. Na przykład - wszystko może się zdarzyć między rozpoczęciem i zakończeniem rozmowy, a Ty mogłeś zdobyć nowe rekwizyty. Zamiast tego zalecamy Suspense do pobierania danych, które będzie wyglądać bardziej jak
const response = MyAPIResource.read();
i żadnych efektów. Ale w międzyczasie możesz przenieść elementy asynchroniczne do oddzielnej funkcji i wywołać ją.
Możesz przeczytać więcej o eksperymentalnym napięciu tutaj .
Jeśli chcesz używać funkcji na zewnątrz z eslint.
function OutsideUsageExample() {
const [data, dataSet] = useState<any>(null)
const fetchMyAPI = useCallback(async () => {
let response = await fetch('api/data')
response = await response.json()
dataSet(response)
}, [])
useEffect(() => {
fetchMyAPI()
}, [fetchMyAPI])
return (
<div>
<div>data: {JSON.stringify(data)}</div>
<div>
<button onClick={fetchMyAPI}>manual fetch</button>
</div>
</div>
)
}
Z useCallback useCallback . Piaskownica .
import React, { useState, useEffect, useCallback } from "react";
export default function App() {
const [counter, setCounter] = useState(1);
const fn = useCallback(() => {
setCounter(counter + 1);
}, [counter]);
useEffect(() => {
if (!(counter % 2)) return;
fn();
}, [fn, counter]);
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 } }, [])
eslint
prosi mnie ofetchMyAPI()
uzależnienieuseEffect
Gdy używasz funkcji asynchronicznej, takiej jak
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); } }
zwraca obietnicę i
useEffect
nie oczekuje, że funkcja zwrotna zwróci Promise, raczej oczekuje, że nic nie zostanie zwrócone lub funkcja zostanie zwrócona.Aby obejść ostrzeżenie, możesz użyć samowywołującej się funkcji asynchronicznej.
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); } })(); }, []);
lub aby uczynić ją bardziej przejrzystą, możesz zdefiniować funkcję, a następnie ją wywołać
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(); }, []);
drugie rozwiązanie ułatwi odczyt i pomoże napisać kod, który anuluje poprzednie żądania, jeśli zostanie uruchomione nowe lub zapisze ostatnią odpowiedź na żądanie w stanie
Działające kodyandbox
źródło
useEffect
możesz zwrócić funkcję do czyszczenia, taką jak anulowanie subskrypcji wydarzeń. Podczas korzystania z funkcji asynchroniczny nie można niczego powrócić, ponieważuseEffect
nie będzie czekać na wynikDopóki React nie zapewni lepszego sposobu, możesz stworzyć pomocnika
useEffectAsync.js
:import { useEffect } from 'react'; export default function useEffectAsync(effect, inputs) { useEffect(() => { effect(); }, inputs); }
Teraz możesz przekazać funkcję asynchroniczną:
useEffectAsync(async () => { const items = await fetchSomeItems(); console.log(items); }, []);
źródło
useAsyncEffect
, jaką napisałeś, może łatwo zmylić kogoś, kto by pomyślał, że jeśli zwróci funkcję porządkującą z efektu asynchronicznego, zostanie ona uruchomiona w odpowiednim czasie. Może to prowadzić do wycieków pamięci lub gorszych błędów, więc zdecydowaliśmy się zachęcić ludzi do refaktoryzacji ich kodu, aby „szew” funkcji asynchronicznych wchodzących w interakcje z cyklem życia Reacta był bardziej widoczny, a zachowanie kodu w rezultacie, miejmy nadzieję, było bardziej przemyślane i poprawne.Przeczytałem to pytanie i czuję, że najlepszy sposób na wdrożenie useEffect nie jest wymieniony w odpowiedziach. Załóżmy, że masz połączenie sieciowe i chcesz coś zrobić, gdy otrzymasz odpowiedź. Dla uproszczenia zapiszmy odpowiedź sieci w zmiennej stanu. Można chcieć użyć akcji / reduktora, aby zaktualizować sklep z odpowiedzią sieciową.
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]);
Odniesienie => https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
źródło
W przypadku innych czytelników błąd może wynikać z faktu, że nie ma nawiasów zawijających funkcję async:
Biorąc pod uwagę funkcję asynchroniczną initData
async function initData() { }
Ten kod doprowadzi do błędu:
useEffect(() => initData(), []);
Ale ten nie:
useEffect(() => { initData(); }, []);
(Zwróć uwagę na nawiasy wokół initData ()
źródło
Można tu użyć operatora void .
Zamiast:
React.useEffect(() => { async function fetchData() { } fetchData(); }, []);
lub
React.useEffect(() => { (async function fetchData() { })() }, []);
mógłbyś napisać:
React.useEffect(() => { void async function fetchData() { }(); }, []);
Jest trochę czystszy i ładniejszy.
Efekty asynchroniczne mogą powodować wycieki pamięci, dlatego ważne jest, aby wykonać czyszczenie po odmontowaniu komponentu. W przypadku pobierania może to wyglądać tak:
function App() { const [data, setData] = React.useState([]) React.useEffect(() => { const abortController = new AbortController() ;(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.log("error", error) } })() return abortController.abort // cancel pending fetch request on component unmount }, []) return <pre>{JSON.stringify(data, null, 2)}</pre> }
źródło
próbować
const MyFunctionnalComponent: React.FC = props => { useEffect(() => { // Using an IIFE (async function anyNameFunction() { await loadContent(); })(); }, []); return <div></div>; };
źródło