UseState bileşeninden durum güncelleyiciye yapılan birden çok çağrı birden çok yeniden işlemeye neden oluyor


114

React kancalarını ilk kez deniyorum ve her şey iyi görünüyordu, ta ki verileri aldığımda ve iki farklı durum değişkenini (veri ve yükleme bayrağı) güncellediğimde, bileşenimin (bir veri tablosu) her iki çağrıya rağmen iki kez işlendiğini fark edene kadar durum güncelleyiciye aynı işlevde oluyor. İşte her iki değişkeni de bileşenime döndüren api fonksiyonum.

const getData = url => {

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

    useEffect(async () => {

        const test = await api.get('/people')

        if(test.ok){
            setLoading(false);
            setData(test.data.results);
        }

    }, []);

    return { data, loading };
};

Normal bir sınıf bileşeninde, durumu güncellemek için tek bir çağrı yaparsınız, bu karmaşık bir nesne olabilir, ancak "kancalı yol" durumu daha küçük birimlere bölmek gibi görünür, bunun bir yan etkisi birden fazla yeniden görünür. ayrı olarak güncellendiğinde oluşturulur. Bunu nasıl azaltacağınıza dair bir fikriniz var mı?


Eyaletlere useReducer
bağlıysanız

Vaov! Ben sadece bunu yeni keşfettim ve reaksiyon oluşturma işleminin nasıl çalıştığına dair anlayışımı tamamen mahvetti. Bu şekilde çalışmanın herhangi bir avantajını anlayamıyorum - eşzamansız bir geri aramadaki davranışın normal bir olay işleyicisinden farklı olması oldukça keyfi görünüyor. BTW, benim testlerimde mutabakatın (yani gerçek DOM'un güncellenmesi) tüm setState çağrıları işlenene kadar gerçekleşmediği, bu nedenle ara render çağrılarının boşa gittiği görülüyor.
Andy

Yanıtlar:


112

Durum loadingve datadurumu tek bir durum nesnesinde birleştirebilir ve sonra bir setStatearama yapabilirsiniz ve yalnızca bir render olacaktır.

Not:setState Sınıf içi bileşenlerden farklı olarak , setStatedöndürülen öğe useStatenesneleri mevcut durumla birleştirmez, nesneyi tamamen değiştirir. Birleştirme yapmak istiyorsanız, önceki durumu okumanız ve yeni değerlerle kendiniz birleştirmeniz gerekir. Dokümanlara bakın .

Bir performans sorununuz olduğunu belirleyene kadar renderları aşırı derecede aramak konusunda fazla endişelenmem. Gerçek DOM'a sanal DOM güncellemelerinin oluşturulması (React bağlamında) ve işlenmesi farklı konulardır. Buradaki oluşturma, sanal DOM'lar oluşturmaya atıfta bulunuyor ve tarayıcının DOM'unu güncellemeyle ilgili değil. React, setStateçağrıları toplu işleyebilir ve tarayıcı DOM'u son yeni durumla güncelleyebilir.

const {useState, useEffect} = React;

function App() {
  const [userRequest, setUserRequest] = useState({
    loading: false,
    user: null,
  });

  useEffect(() => {
    // Note that this replaces the entire object and deletes user key!
    setUserRequest({ loading: true });
    fetch('https://randomuser.me/api/')
      .then(results => results.json())
      .then(data => {
        setUserRequest({
          loading: false,
          user: data.results[0],
        });
      });
  }, []);

  const { loading, user } = userRequest;

  return (
    <div>
      {loading && 'Loading...'}
      {user && user.name.first}
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>

Alternatif - kendi devlet birleşme kancanızı yazın

const {useState, useEffect} = React;

function useMergeState(initialState) {
  const [state, setState] = useState(initialState);
  const setMergedState = newState => 
    setState(prevState => Object.assign({}, prevState, newState)
  );
  return [state, setMergedState];
}

function App() {
  const [userRequest, setUserRequest] = useMergeState({
    loading: false,
    user: null,
  });

  useEffect(() => {
    setUserRequest({ loading: true });
    fetch('https://randomuser.me/api/')
      .then(results => results.json())
      .then(data => {
        setUserRequest({
          loading: false,
          user: data.results[0],
        });
      });
  }, []);

  const { loading, user } = userRequest;

  return (
    <div>
      {loading && 'Loading...'}
      {user && user.name.first}
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>


1
Bunun ideal olmadığına katılıyorum ama yine de makul bir yol. Birleştirmeyi kendiniz yapmak istemiyorsanız, birleştirme yapan kendi özel kancanızı yazabilirsiniz. Son cümleyi daha net olması için güncelledim. React'in nasıl çalıştığını biliyor musunuz? Değilse, daha iyi anlamanıza yardımcı olabilecek bazı bağlantıları burada bulabilirsiniz - reactjs.org/docs/reconciliation.html , blog.vjeux.com/2013/javascript/react-performance.html .
Yangshun Tay

2
@jonhobbs Durumu useMergeStateotomatik olarak birleştirebilmeniz için kanca oluşturan alternatif bir yanıt ekledim .
Yangshun Tay

1
Süper, tekrar teşekkürler. React'i kullanmaya alışmaya başladım, ancak bunun dahili bileşenleri hakkında çok şey öğrenebilirim. Onları okuyacağım.
jonhobbs

2
Hangi koşullarda bunun doğru olabileceğini merak mı ediyorsunuz ?: " React, setStateçağrıları toplu
işleyebilir

1
Güzel .. Durumu birleştirmeyi veya ayrı bir yük durumu kullanmayı ve diğer durumların yük nesnesinden okunmasını sağlamayı düşündüm. Yine de çok iyi bir gönderi. 10/10 tekrar okuyacak
Anthony Moon Beam Toorie

59

Bunun başka bir çözümü var useReducer! önce yenimizi tanımlıyoruz setState.

const [state, setState] = useReducer(
  (state, newState) => ({...state, ...newState}),
  {loading: true, data: null, something: ''}
)

bundan sonra onu eski güzel sınıflar gibi kullanabiliriz this.setState, sadece this!

setState({loading: false, data: test.data.results})

Yenimizde de fark edebileceğiniz setStategibi (tıpkı daha önce sahip olduğumuz gibi this.setState), tüm durumları birlikte güncellememiz gerekmez! örneğin, durumlarımızdan birini şu şekilde değiştirebilirim (ve diğer durumları değiştirmez!):

setState({loading: false})

Harika, Ha ?!

Öyleyse tüm parçaları bir araya getirelim:

import {useReducer} from 'react'

const getData = url => {
  const [state, setState] = useReducer(
    (state, newState) => ({...state, ...newState}),
    {loading: true, data: null}
  )

  useEffect(async () => {
    const test = await api.get('/people')
    if(test.ok){
      setState({loading: false, data: test.data.results})
    }
  }, [])

  return state
}

Expected 0 arguments, but got 1.ts(2554)useReducer'ı bu şekilde kullanmaya çalışırken aldığım bir hatadır. Daha doğrusu setState. Bunu nasıl düzeltirsin? Dosya js, ancak yine de ts kontrolleri kullanıyoruz.
ZenVentzi

Harika kardeşim, Teşekkürler
Nisharg Shah

Sanırım iki devletin birleştiği düşüncesi hala aynı. Ancak bazı durumlarda eyaletleri birleştiremezsiniz.
user1888955

43

React-hook'larda toplu güncelleme güncellemesi https://github.com/facebook/react/issues/14259

React şu anda, bir düğme tıklaması veya giriş değişikliği gibi React tabanlı bir olaydan tetiklenirlerse, güncellemeleri toplu olarak işler. Zaman uyumsuz çağrı gibi bir React olay işleyicisinin dışında tetiklenirlerse güncellemeleri toplu olarak yapmaz.


5
Bu çok yararlı bir cevap çünkü muhtemelen çoğu durumda sorunu hiçbir şey yapmak zorunda kalmadan çözüyor. Teşekkürler!
davnicwil

7

Bu yapacak:

const [state, setState] = useState({ username: '', password: ''});

// later
setState({
    ...state,
    username: 'John'
});

4

Üçüncü taraf kancaları kullanıyorsanız ve durumu tek bir nesnede birleştiremiyorsanız veya kullanamıyorsanız useReducer, çözüm şunları kullanmaktır:

ReactDOM.unstable_batchedUpdates(() => { ... })

Dan Abramov tarafından burada önerildi

Bu örneğe bakın


Yaklaşık bir yıl sonra bu özellik hala tamamen belgelenmemiş gibi görünüyor ve hala kararsız olarak işaretleniyor :-(
Andy

4

Çoğaltmak için this.setStatesınıf bileşenlerden birleştirme davranışı, dokümanlar tepki tavsiye fonksiyonel formunu kullanmak için useStatenesne yayıldı - gerek için useReducer:

setState(prevState => {
  return {...prevState, loading, data};
});

İki durum artık tek bir durumda birleştirildi, bu da size bir işleme döngüsünden tasarruf etmenizi sağlayacak.

Orada bir devlet nesnesi ile başka bir avantajdır: loadingve dataolan bağımlı devletler. Durum bir araya getirildiğinde geçersiz durum değişiklikleri daha belirgin hale gelir:

setState({ loading: true, data }); // ups... loading, but we already set data

Hatta daha iyi olabilir tutarlı durumları sağlamak yapma) 1 ile durumu - loading, success, errorvb - açık kullanarak devlet ve 2.) 'de useReducerbir redüktör içinde kapsülü devlet mantığına:

const useData = () => {
  const [state, dispatch] = useReducer(reducer, /*...*/);

  useEffect(() => {
    api.get('/people').then(test => {
      if (test.ok) dispatch(["success", test.data.results]);
    });
  }, []);
};

const reducer = (state, [status, payload]) => {
  if (status === "success") return { ...state, data: payload, status };
  // keep state consistent, e.g. reset data, if loading
  else if (status === "loading") return { ...state, data: undefined, status };
  return state;
};

const App = () => {
  const { data, status } = useData();
  return status === "loading" ? <div> Loading... </div> : (
    // success, display data 
  )
}

Not: emin olun önek ile özel Hooks'un use( useDatayerine getData). Ayrıca useEffectolamaz için geri arama geçti async.


1
Buna olumlu oy verdi! Bu, React API'de bulduğum en yararlı özelliklerden biri. Bir işlevi bir durum olarak saklamaya çalıştığımda buldum (işlevleri saklamanın tuhaf olduğunu biliyorum, ancak nedense hatırlamıyorum) ve bu ayrılmış çağrı imzası nedeniyle beklendiği gibi çalışmadı
elquimista

1

Https://stackoverflow.com/a/53575023/121143'e cevap vermek için küçük bir ekleme

Güzel! Bu kancayı kullanmayı planlayanlar için, argüman olarak işlevle çalışmak için biraz sağlam bir şekilde yazılabilir, örneğin:

const useMergedState = initial => {
  const [state, setState] = React.useState(initial);
  const setMergedState = newState =>
    typeof newState == "function"
      ? setState(prevState => ({ ...prevState, ...newState(prevState) }))
      : setState(prevState => ({ ...prevState, ...newState }));
  return [state, setMergedState];
};

Güncelleme : optimize edilmiş sürüm, gelen kısmi durum değiştirilmediğinde durum değiştirilmeyecektir.

const shallowPartialCompare = (obj, partialObj) =>
  Object.keys(partialObj).every(
    key =>
      obj.hasOwnProperty(key) &&
      obj[key] === partialObj[key]
  );

const useMergedState = initial => {
  const [state, setState] = React.useState(initial);
  const setMergedState = newIncomingState =>
    setState(prevState => {
      const newState =
        typeof newIncomingState == "function"
          ? newIncomingState(prevState)
          : newIncomingState;
      return shallowPartialCompare(prevState, newState)
        ? prevState
        : { ...prevState, ...newState };
    });
  return [state, setMergedState];
};

Güzel! Ben sadece örtecek setMergedStateile useMemo(() => setMergedState, [])dokümanlar söylediği gibi, çünkü, setStateyeniden vermektedir arasındaki değişmez: React guarantees that setState function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list., setState fonksiyonu üzerinde yeniden oluşturulur değil bu şekilde yeniden oluşturur.
tonix

0

Ayrıca useEffectbir durum değişikliğini algılamak ve diğer durum değerlerini buna göre güncellemek için de kullanabilirsiniz .

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.