Kullanımdaki eşzamansız işlev için React Hook UyarılarıEffect: useEffect işlevi bir temizleme işlevi döndürmeli veya hiçbir şey döndürmemelidir


155

Deniyordum useEffectgibi aşağıda örnek şey:

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);
    }
}, []);

ve konsolumda bu uyarıyı alıyorum. Ancak temizlik, eşzamansız aramalar için isteğe bağlı olduğunu düşünüyorum. Neden bu uyarıyı aldığımı bilmiyorum. Örnekler için korumalı alanı bağlama. https://codesandbox.io/s/24rj871r0p görüntü açıklamasını buraya girin

Yanıtlar:


222

Dan Abramov'un (React çekirdek geliştiricilerinden biri) cevabına buradan bakmanızı öneririm :

Sanırım bunu olması gerekenden daha karmaşık hale getiriyorsun.

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

Daha uzun vadede bu modelden vazgeçeceğiz çünkü yarış koşullarını teşvik ediyor. Örneğin - aramanızın başlangıcı ve bitişi arasında herhangi bir şey olabilir ve yeni donanımlar edinmiş olabilirsiniz. Bunun yerine, daha çok benzer görünecek olan veri getirme için Suspense'i önereceğiz.

const response = MyAPIResource.read();

ve etkisi yok. Ancak bu arada eşzamansız şeyleri ayrı bir işleve taşıyıp çağırabilirsiniz.

Deneysel gerilim hakkında daha fazla bilgiyi buradan okuyabilirsiniz .


Eslint ile dışarıdaki işlevleri kullanmak istiyorsanız.

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

UseCallback useCallback ile . Sandbox .

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

Yarış durumu sorunlarını, bileşenin şu şekilde kaldırılıp kaldırılmadığını kontrol ederek çözebilirsiniz: useEffect(() => { let unmounted = false promise.then(res => { if (!unmounted) { setState(...) } }) return () => { unmounted = true } }, [])
Richard

2
Use-async-effect adlı bir paket de kullanabilirsiniz . Bu paket, zaman uyumsuz bekleme sözdizimini kullanmanızı sağlar.
KittyCat

Kendi kendini çağıran bir işlevi kullanmak, useEffect işlev tanımına zaman uyumsuz sızıntısına veya useEffect etrafında bir sarmalayıcı olarak eşzamansız çağrıyı tetikleyen bir işlevin özel bir uygulamasına izin vermez. Önerilen kullanım-eşzamansız-etki gibi yeni bir paket dahil edebilirsiniz, ancak bunun çözülmesi gereken basit bir problem olduğunu düşünüyorum.
Thulani Chivandikwa

2
hey bu iyi ve çoğu zaman yaptığım şey. ancak eslintbenden fetchMyAPI()bağımlı olmamı istiyoruseEffect
Prakash Reddy Potlapadu

55

Gibi bir zaman uyumsuz işlev kullandığınızda

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

bir söz verir ve useEffectgeri çağırma işlevinin Promise'ı döndürmesini beklemez, bunun yerine hiçbir şeyin döndürülmemesini veya bir işlevin döndürülmesini bekler.

Uyarı için geçici bir çözüm olarak, kendi kendine çağrılan bir zaman uyumsuz işlev kullanabilirsiniz.

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);
        }
    })();
}, []);

veya onu daha temiz hale getirmek için bir işlevi tanımlayabilir ve ardından

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();
}, []);

ikinci çözüm, okumayı kolaylaştıracak ve yenisi çalıştırılırsa önceki istekleri iptal etmek için kod yazmanıza veya en son istek yanıtını duruma kaydetmenize yardımcı olacaktır.

Çalışma kodları ve kutusu


Bunu kolaylaştırmak için bir paket yapıldı. Burada bulabilirsiniz .
KittyCat

1
ama eslint buna tahammül etmeyecek
Muhaimin CS

1
temizleme / didmount geri aramasını yürütmenin bir yolu yok
David Rearte

1
@ShubhamKhatri'yi kullandığınızda useEffect, etkinliklere aboneliğinizi iptal etmek gibi temizlik yapmak için bir işlev döndürebilirsiniz. Eşzamansız işlevi kullandığınızda hiçbir şey geri dönemezsiniz çünkü useEffectsonucu beklemeyeceksiniz
David Rearte

2
eşzamansız olana temizleme işlevi koyabileceğimi mi söylüyorsun? denedim ama temizleme işlevim hiç çağrılmadı. Küçük bir örnek verebilir misin?
David Rearte

36

React daha iyi bir yol sağlayana kadar, bir yardımcı oluşturabilirsiniz useEffectAsync.js:

import { useEffect } from 'react';


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

Şimdi bir eşzamansız işlevi geçebilirsiniz:

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

Güncelleme

Bu yaklaşımı seçerseniz, bunun kötü bir form olduğunu unutmayın. Güvenli olduğunu bildiğimde buna başvuruyorum, ama her zaman kötü bir form ve gelişigüzel.

Hâlâ deneysel olan Veri Getirme için Askıya Alma bazı vakaları çözecektir.

Diğer durumlarda, eşzamansız sonuçları olaylar olarak modelleyebilirsiniz, böylece bileşen yaşam döngüsüne göre bir dinleyici ekleyebilir veya kaldırabilirsiniz.

Veya eşzamansız sonuçları bir Gözlemlenebilir olarak modelleyebilirsiniz, böylece bileşen yaşam döngüsüne göre abone olabilir ve abonelikten çıkabilirsiniz.


9
React'in kullanımda eşzamansız işlevlere otomatik olarak izin vermemesinin nedeniEffect, çoğu durumda, bazı temizleme işlemlerinin gerekli olmasıdır. Fonksiyon useAsyncEffectYazdığınız gibi onlar uygun zamanda çalışacak olacağını onların zaman uyumsuz etkisinden bir temizleme işlevi düşünce içine kolayca saptırmak birisini geri dönebilirler. Bu, bellek sızıntılarına veya daha kötü hatalara yol açabilir, bu nedenle insanları React'in yaşam döngüsüyle etkileşime giren eşzamansız işlevlerin "dikişini" daha görünür kılmak ve sonuç olarak kodun davranışını umarız daha kasıtlı ve doğru hale getirmek için kodlarını yeniden düzenlemeye teşvik etmeyi seçtik.
Sophie Alpert

9

Bu soruyu okudum ve useEffect'i uygulamanın en iyi yolunun cevaplarda belirtilmediğini hissediyorum. Diyelim ki bir şebeke aramanız var ve yanıtı aldığınızda bir şeyler yapmak istiyorsunuz. Basit olması açısından ağ yanıtını bir durum değişkeninde saklayalım. Mağazayı ağ yanıtıyla güncellemek için eylem / azaltıcı kullanmak isteyebilirsiniz.

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]);

Referans => https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects


6

void operatörü burada kullanılabilir.
Onun yerine:

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

veya

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

yazabilirsin:

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

Biraz daha temiz ve güzel.


Zaman uyumsuz etkiler bellek sızıntılarına neden olabilir, bu nedenle, ayrılan bileşen üzerinde temizleme işlemi gerçekleştirmek önemlidir. Getirme durumunda bu şöyle görünebilir:

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


JS'nizi biliyorsunuz efendim. AbortController, iptal edilebilir vaat teklifleri başarısız olduktan sonra sunulan yeni moda şeydir
Devin G Rhode

BTW orada bir "kullan-durdurulabilir-getir" paketi var, ancak yazılma şeklini sevdiğimden emin değilim. Burada özel bir kanca olarak sahip olduğunuz bu kodun basit bir versiyonunu almanız güzel olurdu. Ayrıca, "await-here", bir dene / yakala bloğu ihtiyacını azaltabilecek oldukça güzel bir pakettir.
Devin G Rhode

5

Diğer okuyucular için hata, zaman uyumsuz işlevi saran parantez bulunmamasından kaynaklanabilir:

İnitData zaman uyumsuz işlevini göz önünde bulundurarak

  async function initData() {
  }

Bu kod, hatanıza yol açacaktır:

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

Ama bu:

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

(İnitData () etrafındaki parantezlere dikkat edin


1
Harika dostum! Saga kullanıyorum ve bu hata, tek nesneyi döndüren bir eylem yaratıcısını ararken ortaya çıktı. Geri çağırma işlevinin useEffect'i bu davranışı yalamıyor gibi görünüyor. Cevabınız için minnettarım.
Gorr1995

3
İnsanlar bunun neden doğru olduğunu merak ediyorlarsa ... Küme parantezleri olmadan, initData () işlevinin dönüş değeri ok işlevi tarafından dolaylı olarak döndürülür. Küme parantezlerinde hiçbir şey örtük olarak döndürülmez ve bu nedenle hata olmaz.
Marnix.hoh

1

Deneyin

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.