Dlaczego funkcja setState jest ustawiona w respondjs Async zamiast w Sync?

126

Właśnie odkryłem, że this.setState()funkcja react w dowolnym komponencie jest asynchroniczna lub jest wywoływana po zakończeniu funkcji, w której została wywołana.

Teraz przeszukałem i znalazłem tego bloga ( setState () Operacja mutacji stanu może być synchroniczna w ReactJS )

Tutaj odkrył, że setStatejest to asynchronizacja (wywoływana, gdy stos jest pusty) lub synchronizacja (wywoływana zaraz po wywołaniu) w zależności od tego, jak wyzwolono zmianę stanu.

Teraz te dwie rzeczy są trudne do strawienia

  1. W blogu setStatefunkcja jest wywoływana wewnątrz funkcji updateState, ale to, co ją wyzwoliło, updateStatenie jest czymś, o czym mogłaby wiedzieć wywoływana funkcja.
  2. Dlaczego mieliby tworzyć setStateasync, ponieważ JS jest językiem jednowątkowym, a to setState nie jest WebAPI ani wywołaniem serwera, więc musi być wykonane tylko w wątku JS. Czy robią to po to, aby ponowne renderowanie nie zatrzymywało wszystkich słuchaczy zdarzeń i rzeczy, czy też jest jakiś inny problem projektowy.
Anup
źródło
1
Napisałem dzisiaj artykuł, który pomaga opisać trochę klimat wokół setState: medium.com/@agm1984/ ...
agm1984

Odpowiedzi:

156

Możesz wywołać funkcję po zaktualizowaniu wartości stanu:

this.setState({foo: 'bar'}, () => { 
    // Do something here. 
});

Ponadto, jeśli masz wiele stanów do jednoczesnej aktualizacji, zgrupuj je wszystkie w tym samym setState:

Zamiast:

this.setState({foo: "one"}, () => {
    this.setState({bar: "two"});
});

Po prostu zrób to:

this.setState({
    foo: "one",
    bar: "two"
});
JoeTidee
źródło
17
Tak, w porządku, mamy funkcję callBack, której możemy użyć, ale to nie jest pytanie.
Anup
12
Miejmy nadzieję, że pomoże to komuś innemu, jak natknie się na to pytanie.
JoeTidee
2
ta informacja może być pomocna
Anup
97

1) setStateakcje są asynchroniczne i są grupowane w celu zwiększenia wydajności. Jest to wyjaśnione w dokumentacji setState.

setState () nie zmienia natychmiast this.state, ale tworzy oczekującą zmianę stanu. Dostęp do this.state po wywołaniu tej metody może potencjalnie zwrócić istniejącą wartość. Nie ma gwarancji synchronicznego działania wywołań funkcji setState, a wywołania mogą być grupowane w celu zwiększenia wydajności.


2) Dlaczego mieliby ustawiać setState jako async, skoro JS jest językiem jednowątkowym, a setStatenie jest to wywołanie WebAPI ani wywołanie serwera?

Dzieje się tak, ponieważ setStatezmienia stan i powoduje ponowne poddanie się. Może to być kosztowna operacja, a synchronizacja może spowodować, że przeglądarka nie będzie odpowiadać.

W związku z tym wywołania setState są asynchroniczne, a także grupowane w celu lepszego działania interfejsu użytkownika i wydajności.

Sachin
źródło
59
Jeśli potrzebujesz zapewnić kolejność zdarzeń po wywołaniu setState, możesz przekazać funkcję zwrotną. this.setState({ something: true }, () => console.log(this.state))
Ianks
1
Dziękuję @Sachin za wyjaśnienie. Jednak nadal mam wątpliwości, czy może być synchroniczne, jak wyjaśnia blog?
Ajay Gaur,
2
Kolejna głupia decyzja projektowa w reakcji. Spraw, aby aktualizacja stanu była synchroniczna, a renderowanie asynchroniczne. Możesz renderować partie, ale chcę mieć możliwość ustawienia czegoś tak prymitywnego jak zmienne stanu bez konieczności radzenia sobie z warunkami wyścigu.
ig-dev
Dlaczego nie pozwolić na ustawienie opcji tak, aby funkcja była synchronizowana lub asynchroniczna? To byłaby przydatna funkcja
Randall Coding
Oczywiście nie należę do zespołu reagowania, ale moim zdaniem jednym z powodów, dla których aktualizowanie stanu jest asynchroniczne, jest to, że przeglądarki są jednowątkowe. Operacje synchronizacji mogą powodować brak odpowiedzi interfejsu użytkownika i nie są dobrym kandydatem do korzystania z interfejsu użytkownika.
Sachin
16

Wiem, że to pytanie jest stare, ale przez długi czas powodowało wiele zamieszania u wielu użytkowników reakcji, w tym mnie. Niedawno Dan Abramov (z zespołu reagowania) właśnie napisał świetne wyjaśnienie, dlaczego natura setStatejest asynchroniczna:

https://github.com/facebook/react/issues/11527#issuecomment-360199710

setStatema być asynchroniczny, a w powiązanym wyjaśnieniu Dana Abramova jest kilka naprawdę dobrych powodów. Nie oznacza to, że zawsze będzie asynchroniczny - oznacza to głównie, że po prostu nie możesz polegać na synchronizacji . ReactJS bierze pod uwagę wiele zmiennych w scenariuszu, w którym zmieniasz stan, aby zdecydować, kiedy statefaktycznie należy zaktualizować i ponownie wyrenderować komponent.
Prostym przykładem, aby to zademonstrować, jest to, że jeśli zadzwonisz setStatejako reakcja na akcję użytkownika, to stateprawdopodobnie zostanie zaktualizowany natychmiast (chociaż znowu nie możesz na to liczyć), więc użytkownik nie odczuje żadnego opóźnienia , ale jeśli zadzwoniszsetState w reakcji na odpowiedź na wywołanie ajax lub inne zdarzenie, które nie jest wyzwalane przez użytkownika, stan może zostać zaktualizowany z niewielkim opóźnieniem, ponieważ użytkownik tak naprawdę nie odczuje tego opóźnienia i poprawi wydajność, czekając na zbiorczo wiele aktualizacji stanu i rzadziej ponownie oddawaj model DOM.

gillyb
źródło
nie zaznaczyłeś żadnej odpowiedzi jako właściwej. Ludzie piszą, jak to obejść. Nie odpowiedź na zadane pytanie. ten artykuł wydaje się dobry.
Anup
@Anup Odpowiedź jest trochę bardziej skomplikowana niż zwykła „asynchronizacja” lub „synchronizacja”. Powinien być zawsze traktowany jako „asynchroniczny”, ale w niektórych przypadkach może działać jako „synchronizacja”. Mam nadzieję, że rzuciłem dla Ciebie trochę światła.
gillyb
8

Dobry artykuł tutaj https://github.com/vasanthk/react-bits/blob/master/patterns/27.passing-function-to-setState.md

// assuming this.state.count === 0
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
// this.state.count === 1, not 3

Solution
this.setState((prevState, props) => ({
  count: prevState.count + props.increment
}));

lub przekaż callback this.setState ({.....},callback)

https://medium.com/javascript-scene/setstate-gate-abc10a9b2d82 https://medium.freecodecamp.org/functional-setstate-is-the-future-of-react-374f30401b6b

zloctb
źródło
7

Możesz użyć następującego zawijania, aby wykonać połączenie synchronizacyjne

this.setState((state =>{
  return{
    something
  }
})

Ярослав
źródło
niedoceniana odpowiedź
James Cat
1

Wyobraź sobie, że zwiększasz licznik w jakimś komponencie:

  class SomeComponent extends Component{

    state = {
      updatedByDiv: '',
      updatedByBtn: '',
      counter: 0
    }

    divCountHandler = () => {
      this.setState({
        updatedByDiv: 'Div',
        counter: this.state.counter + 1
      });
      console.log('divCountHandler executed');
    }

    btnCountHandler = () => {
      this.setState({
        updatedByBtn: 'Button',
        counter: this.state.counter + 1
      });
      console.log('btnCountHandler executed');
    }
    ...
    ...
    render(){
      return (
        ...
        // a parent div
        <div onClick={this.divCountHandler}>
          // a child button
          <button onClick={this.btnCountHandler}>Increment Count</button>
        </div>
        ...
      )
    }
  }

Istnieje procedura obsługi zliczania dołączona zarówno do składnika nadrzędnego, jak i podrzędnego. Odbywa się to celowo, abyśmy mogli dwukrotnie wykonać setState () w tym samym kontekście propagacji zdarzenia kliknięcia, ale z poziomu 2 różnych programów obsługi.

Jak możemy sobie wyobrazić, pojedyncze zdarzenie kliknięcia przycisku wyzwoli teraz oba te programy obsługi, ponieważ zdarzenie przechodzi z celu do najbardziej zewnętrznego kontenera podczas fazy propagacji.

Dlatego btnCountHandler () jest wykonywana jako pierwsza, oczekuje się, że zwiększy licznik do 1, a następnie wykonuje divCountHandler (), oczekując, że zwiększy licznik do 2.

Jednak liczba wzrasta tylko do 1, co można sprawdzić w narzędziach React Developer.

To dowodzi, że reagujemy

  • kolejkuje wszystkie wywołania setState

  • wraca do tej kolejki po wykonaniu ostatniej metody w kontekście (w tym przypadku divCountHandler)

  • łączy wszystkie mutacje obiektów zachodzące w wielu wywołaniach setState w tym samym kontekście (wszystkie wywołania metod w jednej fazie zdarzenia mają ten sam kontekst np.) w jedną składnię mutacji obiektu (scalanie ma sens, ponieważ dlatego możemy niezależnie aktualizować właściwości stanu w setState () na pierwszym miejscu)

  • i przekazuje go do jednej pojedynczej metody setState (), aby zapobiec ponownemu renderowaniu z powodu wielu wywołań setState () (jest to bardzo prymitywny opis przetwarzania wsadowego).

Wynikowy kod uruchomiony przez React:

this.setState({
  updatedByDiv: 'Div',
  updatedByBtn: 'Button',
  counter: this.state.counter + 1
})

Aby zatrzymać to zachowanie, zamiast przekazywania obiektów jako argumentów do metody setState, przekazywane są wywołania zwrotne.

    divCountHandler = () => {
          this.setState((prevState, props) => {
            return {
              updatedByDiv: 'Div',
              counter: prevState.counter + 1
            };
          });
          console.log('divCountHandler executed');
        }

    btnCountHandler = () => {
          this.setState((prevState, props) => {
            return {
              updatedByBtn: 'Button',
              counter: prevState.counter + 1
            };
          });
      console.log('btnCountHandler executed');
    }

Gdy ostatnia metoda zakończy wykonywanie i gdy reakcja powróci do przetworzenia kolejki setState, po prostu wywołuje wywołanie zwrotne dla każdego ustawionego w kolejce setState, przekazując poprzedni stan komponentu.

W ten sposób reakcja zapewnia, że ​​ostatnie wywołanie zwrotne w kolejce zaktualizuje stan, w którym wszystkie poprzednie odpowiedniki położyły w ręce.

supi
źródło
0

Tak, metoda setState () jest asynchroniczna.

Z linku: https://reactjs.org/docs/react-component.html#setstate

  • React nie gwarantuje, że zmiany stanu zostaną zastosowane natychmiast.
  • setState () nie zawsze od razu aktualizuje komponent.
  • Traktuj setState () jako żądanie, a nie natychmiastowe polecenie aktualizacji komponentu.

Ponieważ myślą
Z linku: https://github.com/facebook/react/issues/11527#issuecomment-360199710

... zgadzamy się, że synchroniczne ponowne renderowanie metody setState () byłoby w wielu przypadkach nieefektywne

Asynchroniczna funkcja setState () bardzo utrudnia życie początkującym, a nawet niestety doświadczyli:
- nieoczekiwanych problemów z renderowaniem: opóźnione renderowanie lub brak renderowania (w oparciu o logikę programu)
- przekazywanie parametrów to ważna sprawa
wśród innych problemów.

Poniższy przykład pomógł:

// call doMyTask1 - here we set state
// then after state is updated...
//     call to doMyTask2 to proceed further in program

constructor(props) {
    // ..

    // This binding is necessary to make `this` work in the callback
    this.doMyTask1 = this.doMyTask1.bind(this);
    this.doMyTask2 = this.doMyTask2.bind(this);
}

function doMyTask1(myparam1) {
    // ..

    this.setState(
        {
            mystate1: 'myvalue1',
            mystate2: 'myvalue2'
            // ...
        },    
        () => {
            this.doMyTask2(myparam1); 
        }
    );
}

function doMyTask2(myparam2) {
    // ..
}

Mam nadzieję, że to pomoże.

Manohar Reddy Poreddy
źródło