ReactJS - Czy render jest wywoływany za każdym razem, gdy wywoływana jest funkcja „setState”?

503

Czy React ponownie renderuje wszystkie komponenty i komponenty podrzędne przy każdym setStatewywołaniu?

Jeśli tak, to dlaczego? Pomyślałem, że pomysł polegał na tym, że React renderował tak mało, jak to konieczne - gdy stan się zmienia.

W poniższym prostym przykładzie obie klasy są renderowane ponownie po kliknięciu tekstu, pomimo faktu, że stan nie zmienia się przy kolejnych kliknięciach, ponieważ program obsługi onClick zawsze ustawia statetę samą wartość:

this.setState({'test':'me'});

Spodziewałbym się, że renderowanie nastąpi tylko w przypadku zmiany statedanych.

Oto kod przykładu, jako skrzypce JS i osadzony fragment:

var TimeInChild = React.createClass({
    render: function() {
        var t = new Date().getTime();

        return (
            <p>Time in child:{t}</p>
        );
    }
});

var Main = React.createClass({
    onTest: function() {
        this.setState({'test':'me'});
    },

    render: function() {
        var currentTime = new Date().getTime();

        return (
            <div onClick={this.onTest}>
            <p>Time in main:{currentTime}</p>
            <p>Click me to update time</p>
            <TimeInChild/>
            </div>
        );
    }
});

ReactDOM.render(<Main/>, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>

[1]: http://jsfiddle.net/fp2tncmb/2/
Brad Parks
źródło
Miałem ten sam problem, nie znam dokładnego rozwiązania. Ale usunąłem niechciane kody z komponentu, który zaczął działać jak zwykle.
Jaison,
Chciałbym zwrócić uwagę, że w twoim przykładzie - ponieważ sposób zaprojektowania elementu nie opiera się wyłącznie na unikalnym stanie - wywołanie setState()nawet z fałszywymi danymi powoduje, że element renderuje się inaczej, więc powiedziałbym „tak”. Absolutnie powinien spróbować ponownie renderować obiekt, gdy coś mogło się zmienić, ponieważ w przeciwnym razie demo - zakładając, że było to zamierzone zachowanie - nie działałoby!
Tadhg McDonald-Jensen
Możesz mieć rację @ TadhgMcDonald-Jensen - ale z mojego zrozumienia React oddałby to za pierwszym razem (ponieważ stan zmienia się z niczego na coś za pierwszym razem), a potem nigdy nie musiałby renderować ponownie. Oczywiście się myliłem - ponieważ wygląda na to, że React wymaga napisania własnej shouldComponentUpdatemetody, która, jak zakładam , musi być zawarta w React. Wygląda na to, że domyślna wersja zawarta w React po prostu zwraca true- co zmusza komponent do ponownego renderowania za każdym razem.
Brad Parks
Tak, ale musi tylko ponownie renderować w wirtualnym DOM, wówczas zmienia rzeczywisty DOM tylko wtedy, gdy komponent jest renderowany inaczej. Aktualizacje wirtualnego DOM są zwykle nieistotne (przynajmniej w porównaniu do modyfikowania rzeczy na rzeczywistym ekranie), więc wywoływanie renderowania za każdym razem, gdy może zajść potrzeba aktualizacji, to stwierdzenie, że żadna zmiana nie nastąpiła, nie jest zbyt droga i bezpieczniejsza niż zakładanie, że powinna renderować to samo.
Tadhg McDonald-Jensen

Odpowiedzi:

570

Czy React ponownie renderuje wszystkie komponenty i podkomponenty przy każdym wywołaniu setState?

Domyślnie - tak.

Istnieje metoda boolean powinnaComponentUpdate (obiekt nextProps, obiekt nextState) , każdy składnik ma tę metodę i jest odpowiedzialny za określenie „czy aktualizacja składnika (uruchomienie funkcji renderowania )?” za każdym razem, gdy zmieniasz stan lub przekazujesz nowe rekwizyty ze składnika nadrzędnego.

Możesz napisać własną implementację metody shouldComponentUpdate dla swojego komponentu, ale domyślna implementacja zawsze zwraca true - co oznacza, że ​​zawsze uruchom ponownie funkcję renderowania.

Cytat z oficjalnych dokumentów http://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate

Domyślnie, shouldComponentUpdate zawsze zwraca true, aby zapobiec subtelnym błędom, gdy stan jest mutowany w miejscu, ale jeśli jesteś ostrożny, aby zawsze traktować ten stan jako niezmienny i tylko do odczytu z rekwizytów i stanu w render (), możesz zastąpić shouldComponentUpdate za pomocą implementacja, która porównuje stare rekwizyty i podaje ich zamienniki.

Następna część twojego pytania:

Jeśli tak, to dlaczego? Pomyślałem, że pomysł polegał na tym, że React renderował tak mało, jak to konieczne - gdy stan się zmieniał.

Istnieją dwa kroki, które możemy nazwać „renderowaniem”:

  1. Renderowanie wirtualnych DOM: po wywołaniu metody renderowania zwraca nową strukturę wirtualnych domen komponentu. Jak już wspomniałem wcześniej, ta metoda renderowania jest wywoływana zawsze podczas wywoływania metody setState () , ponieważ powinna domyślnie zwracać wartość true dla parametru trueComponentUpdate . Tak więc domyślnie w React nie ma optymalizacji.

  2. Natywne rendery DOM: React zmienia rzeczywiste węzły DOM w przeglądarce tylko wtedy, gdy zostały zmienione w Virtual DOM i tak mało, jak to konieczne - jest to świetna funkcja React, która optymalizuje prawdziwą mutację DOM i sprawia, że ​​React jest szybki.

Petr
źródło
47
Świetne wyjaśnienie! Jedną z rzeczy, która naprawdę sprawia, że ​​React wciąż świeci w tym przypadku, jest to, że nie zmienia on prawdziwej DOM, chyba że wirtualna DOM została zmieniona. Więc jeśli moja funkcja renderowania zwraca tę samą rzecz 2 razy z rzędu, prawdziwy DOM w ogóle się nie zmienia ... Dzięki!
Brad Parks,
1
Myślę, że @tungd ma rację - patrz jego odpowiedź poniżej. Jest to również kwestia relacji rodzic-dziecko między komponentami. Na przykład, jeśli TimeInChild jest rodzeństwem Main, wówczas funkcja render () nie będzie wywoływana niepotrzebnie.
gvlax
2
Jeśli twój stan zawiera stosunkowo duży obiekt javascript (~ 1k łącznie właściwości), który jest renderowany jako duże drzewo komponentów (~ 100 łącznie) ... czy powinieneś pozwolić funkcjom renderującym zbudować dom wirtualny, czy też powinieneś, przed ustawieniem stan, porównaj nowy stan ze starym ręcznie i zadzwoń tylko setStatewtedy, gdy wykryjesz różnicę? Jeśli tak, jak to zrobić najlepiej - porównać ciągi JSON, skonstruować i porównać skróty obiektów ...?
Vincent Sels
1
@Petr, ale mimo reakcji rekonstrukcji wirtualnej domeny, jeśli stara domena wirtualna jest taka sama jak nowa domena wirtualna, domena przeglądarki nie zostanie zmieniona, prawda?
Jaskey,
3
Zobacz także użycie React.PureComponent ( reagjs.org/docs/react-api.html#reactpurecomponent ). Aktualizuje się (ponownie renderuje) tylko wtedy, gdy stan komponentu lub rekwizyty rzeczywiście się zmieniły. Uważaj jednak, aby porównanie było płytkie.
debatuje
105

Nie, React nie renderuje wszystkiego, gdy zmienia się stan.

  • Za każdym razem, gdy komponent jest brudny (jego stan się zmienia), ten komponent i jego elementy potomne są renderowane ponownie. W pewnym stopniu ma to na celu jak najmniejsze ponowne renderowanie. Jedynym momentem, w którym renderowanie nie jest wywoływane, jest przeniesienie gałęzi do innego katalogu głównego, gdzie teoretycznie nie musimy niczego renderować ponownie. W twoim przykładzie TimeInChildjest to element podrzędny Main, więc jest również renderowany ponownie, gdy stan Mainzmian.

  • React nie porównuje danych o stanie. Kiedy setStatejest wywoływane, oznacza komponent jako brudny (co oznacza, że ​​należy go ponownie renderować). Ważną rzeczą do zapamiętania jest to, że chociaż renderwywoływana jest metoda komponentu, prawdziwy DOM jest aktualizowany tylko wtedy, gdy dane wyjściowe są inne niż bieżące drzewo DOM (inaczej różnicowanie między wirtualnym drzewem DOM a drzewem DOM dokumentu). W twoim przykładzie, mimo że statedane nie uległy zmianie, zmienił się czas ostatniej zmiany, dzięki czemu wirtualny DOM różni się od DOM dokumentu, dlatego też HTML jest aktualizowany.

wolfram
źródło
Tak, to poprawna odpowiedź. W ramach eksperymentu zmodyfikuj ostatni wiersz jako React.renderComponent (<div> <Main /> <TimeInChild /> </div>, document.body) ; i usunąć <TimeInChild /> z korpusu tynku () od głównego składnika. Render () z TimeInChild nie zostanie wywołana domyślnie, ponieważ nie jest dzieckiem Menem więcej.
gvlax
Dzięki, takie rzeczy są nieco skomplikowane, dlatego autorzy React zalecili, aby render()metoda była „czysta” - niezależna od stanu zewnętrznego.
tungd
3
@tungd, co to znaczy some branch is moved to another root? Co pan zadzwonić branch? Co pan zadzwonić root?
Zielony
2
Naprawdę wolę odpowiedź oznaczoną jako poprawną. Rozumiem twoją interpretację „renderowania” po stronie rodzimej, „rzeczywistej” ... ale jeśli spojrzysz na to od strony reakcyjnej, musisz powiedzieć, że renderuje ponownie. Na szczęście jest wystarczająco inteligentny, aby określić, co naprawdę się zmieniło i tylko zaktualizować te rzeczy. Ta odpowiedź może być myląca dla nowych użytkowników, najpierw powiesz „nie”, a następnie wyjaśnisz, że rzeczy się
renderują
1
@tungd czy możesz wyjaśnić pytanie what does it mean some branch is moved to another root? What do you call branch? What do you call root?
Greena
7

Chociaż jest to stwierdzone w wielu innych odpowiedziach tutaj, komponent powinien:

  • zaimplementuj, shouldComponentUpdateaby renderować tylko w przypadku zmiany stanu lub właściwości

  • przełącz się na rozszerzenie PureComponent , który już implementuje shouldComponentUpdatewewnętrznie metodę płytkich porównań.

Oto przykład zastosowań shouldComponentUpdate, który działa tylko w tym prostym przypadku użycia i celach demonstracyjnych. Gdy jest to używane, komponent nie renderuje się ponownie przy każdym kliknięciu i jest renderowany przy pierwszym wyświetleniu, a po kliknięciu raz.

var TimeInChild = React.createClass({
    render: function() {
        var t = new Date().getTime();

        return (
            <p>Time in child:{t}</p>
        );
    }
});

var Main = React.createClass({
    onTest: function() {
        this.setState({'test':'me'});
    },

    shouldComponentUpdate: function(nextProps, nextState) {
      if (this.state == null)
        return true;
  
      if (this.state.test == nextState.test)
        return false;
        
      return true;
  },

    render: function() {
        var currentTime = new Date().getTime();

        return (
            <div onClick={this.onTest}>
            <p>Time in main:{currentTime}</p>
            <p>Click me to update time</p>
            <TimeInChild/>
            </div>
        );
    }
});

ReactDOM.render(<Main/>, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>

Brad Parks
źródło
6

Tak. Wywołuje metodę render () za każdym razem, gdy wywołujemy metodę setState, gdy „shouldComponentUpdate” zwraca wartość false.

lakmal_sathyajith
źródło
7
Bądź bardziej rozbudowany
Karthick Ramesh
3

Kolejnym powodem „zagubionej aktualizacji” może być następująca:

  • Jeśli zdefiniowano statyczny getDerivedStateFromProps, jest on ponownie uruchamiany w każdym procesie aktualizacji zgodnie z oficjalną dokumentacją https://reactjs.org/docs/react-component.html#updating .
  • więc jeśli ta wartość stanu pochodzi z rekwizytów na początku, jest nadpisywana przy każdej aktualizacji.

Jeśli to jest problem, U może uniknąć ustawienia stanu podczas aktualizacji, należy sprawdzić wartość parametru stanu w ten sposób

static getDerivedStateFromProps(props: TimeCorrectionProps, state: TimeCorrectionState): TimeCorrectionState {
   return state ? state : {disable: false, timeCorrection: props.timeCorrection};
}

Innym rozwiązaniem jest dodanie zainicjowanej właściwości do stanu i skonfigurowanie jej po raz pierwszy (jeśli stan jest inicjowany na wartość inną niż null).

Zoltán Krizsán
źródło
0

Nie wszystkie elementy.

statew wyglądzie składowych jak źródło wodospadu stanu całego APP.

Tak więc zmiana następuje z miejsca, w którym wywołano setState. rendersStamtąd zostaje wywołane drzewo . Jeśli użyłeś czystego komponentu, renderzostanie on pominięty.

Singhi John
źródło