Reaguje funkcjonalny składnik bezstanowy, PureComponent, Component; jakie są różnice i kiedy powinniśmy z czego korzystać?

189

Dowiedzieliśmy się , że od wersji 15.3.0 React mamy nową klasę podstawową o nazwie PureComponent, która może być rozszerzona o wbudowany PureRenderMixin . Rozumiem, że pod maską stosuje się płytkie porównanie rekwizytów w środku shouldComponentUpdate.

Teraz mamy 3 sposoby na zdefiniowanie komponentu React:

  1. Funkcjonalny składnik bezstanowy, który nie rozszerza żadnej klasy
  2. Składnik rozszerzający PureComponentklasę
  3. Normalny komponent rozszerzający Componentklasę

Jakiś czas temu nazywaliśmy komponenty bezstanowe Pure Components, a nawet Dumb Components. Wygląda na to, że cała definicja słowa „czysty” zmieniła się w React.

Chociaż rozumiem podstawowe różnice między tymi trzema, wciąż nie jestem pewien, kiedy wybrać co . Jakie są również wpływy na wydajność i kompromisy każdego z nich?


Aktualizacja :

Oto pytanie, które należy wyjaśnić:

  • Czy powinienem zdefiniować moje proste komponenty jako funkcjonalne (ze względu na prostotę) czy rozszerzyć PureComponentklasę (ze względu na wydajność)?
  • Czy zwiększenie wydajności, które otrzymałem, jest prawdziwym kompromisem za utraconą prostotę?
  • Czy kiedykolwiek będę musiał rozszerzyć normalną Componentklasę, gdy zawsze będę mógł korzystać PureComponentz lepszej wydajności?
Yadhu Kiran
źródło

Odpowiedzi:

315

Jak decydujesz, jak wybierasz pomiędzy tymi trzema w zależności od celu / rozmiaru / rekwizytów / zachowania naszych komponentów?

Rozszerzanie z React.PureComponentlub React.Componentz niestandardową shouldComponentUpdatemetodą ma wpływ na wydajność. Korzystanie z bezpaństwowych komponentów funkcjonalnych jest wyborem „architektonicznym” i nie przynosi żadnych korzyści wydajności od razu po wyjęciu z pudełka.

  • W przypadku prostych, tylko prezentacyjnych komponentów, które muszą być łatwo ponownie użyte, preferuj bezstanowe komponenty funkcjonalne. W ten sposób masz pewność, że są one oddzielone od rzeczywistej logiki aplikacji, że są niezwykle łatwe do przetestowania i że nie mają nieoczekiwanych efektów ubocznych. Wyjątkiem jest sytuacja, gdy z jakiegoś powodu jest ich dużo lub jeśli naprawdę trzeba zoptymalizować ich metodę renderowania (ponieważ nie można zdefiniować shouldComponentUpdatedla funkcjonalnego komponentu bezstanowego).

  • Rozszerz, PureComponentjeśli wiesz, że twój wynik zależy od prostych rekwizytów / stanu („prosty” oznacza brak zagnieżdżonych struktur danych, ponieważ PureComponent wykonuje płytkie porównanie) ORAZ potrzebujesz / możesz uzyskać pewne ulepszenia wydajności.

  • Rozszerz Componenti zaimplementuj własne, shouldComponentUpdatejeśli potrzebujesz wzrostu wydajności, wykonując niestandardową logikę porównania pomiędzy następnymi / bieżącymi rekwizytami i stanem. Na przykład możesz szybko przeprowadzić głębokie porównanie za pomocą lodash # isEqual:

    class MyComponent extends Component {
        shouldComponentUpdate (nextProps, nextState) {
            return !_.isEqual(this.props, nextProps) || !_.isEqual(this.state, nextState);
        }
    }

Wdrażanie własnych shouldComponentUpdatelub rozszerzanie z nich PureComponentto również optymalizacje i jak zwykle powinieneś zacząć je analizować tylko w przypadku problemów z wydajnością ( unikaj przedwczesnych optymalizacji ). Zasadniczo zawsze staram się przeprowadzać te optymalizacje, gdy aplikacja jest w stanie roboczym, a większość funkcji jest już zaimplementowana. O wiele łatwiej jest skupić się na problemach z wydajnością, gdy faktycznie przeszkadzają.

Więcej szczegółów

Funkcjonalne komponenty bezstanowe:

Są one definiowane za pomocą funkcji. Ponieważ nie ma stanu wewnętrznego dla komponentu bezstanowego, wynik (co jest renderowane) zależy tylko od rekwizytów podanych jako dane wejściowe dla tej funkcji.

Plusy:

  • Najprostszy możliwy sposób zdefiniowania komponentu w React. Jeśli nie musisz zarządzać żadnym stanem, po co zawracać sobie głowę klasami i dziedziczeniem? Jedną z głównych różnic między funkcją a klasą jest to, że dzięki funkcji masz pewność, że wynik zależy tylko od danych wejściowych (a nie od historii poprzednich wykonań).

  • Najlepiej w swojej aplikacji powinieneś dążyć do posiadania jak największej liczby elementów bezstanowych, ponieważ zwykle oznacza to, że przeniosłeś logikę poza warstwę widoku i przeniosłeś ją do czegoś takiego jak redux, co oznacza, że ​​możesz przetestować swoją prawdziwą logikę bez konieczności renderowania czegokolwiek (znacznie łatwiejsze do przetestowania, bardziej wielokrotnego użytku itp.).

Cons:

  • Brak metod cyklu życia. Nie możesz zdefiniować componentDidMountinnych znajomych. Zwykle robisz to w komponencie nadrzędnym znajdującym się wyżej w hierarchii, abyś mógł zamienić wszystkie dzieci w bezpaństwowe.

  • Nie ma możliwości ręcznego kontrolowania, kiedy ponowne renderowanie jest potrzebne, ponieważ nie można go zdefiniować shouldComponentUpdate. Ponowne renderowanie odbywa się za każdym razem, gdy komponent otrzymuje nowe rekwizyty (brak możliwości płytkiego porównania itp.). W przyszłości React może automatycznie optymalizować bezstanowe komponenty, na razie jest kilka bibliotek, z których można korzystać. Ponieważ komponenty bezstanowe są tylko funkcjami, jest to w zasadzie klasyczny problem „zapamiętywania funkcji”.

  • Referencje nie są obsługiwane: https://github.com/facebook/react/issues/4936

Składnik rozszerzający klasę PureComponent VS Zwykły składnik rozszerzający klasę Component:

React miał PureRenderMixinkiedyś możliwość dołączenia do klasy zdefiniowanej za pomocą React.createClassskładni. Mixin po prostu zdefiniuje shouldComponentUpdatewykonanie płytkiego porównania między następnymi rekwizytami i następnym stanem, aby sprawdzić, czy coś się tam zmieniło. Jeśli nic się nie zmieni, nie ma potrzeby ponownego renderowania.

Jeśli chcesz użyć składni ES6, nie możesz używać miksów. Więc dla wygody React wprowadził PureComponentklasę, z której można dziedziczyć zamiast korzystać Component. PureComponentpo prostu implementuje shouldComponentUpdatew taki sam sposób jak PureRendererMixin. Jest to przede wszystkim wygoda, więc nie musisz sam go wdrażać, ponieważ płytkie porównanie obecnego / następnego stanu z rekwizytami jest prawdopodobnie najczęstszym scenariuszem, który może dać ci kilka szybkich zwycięstw w wydajności.

Przykład:

class UserAvatar extends Component {
    render() {
       return <div><img src={this.props.imageUrl} /> {{ this.props.username }} </div>
    }
} 

Jak widać, wyniki zależą od props.imageUrli props.username. Jeśli w komponencie nadrzędnym renderujesz <UserAvatar username="fabio" imageUrl="http://foo.com/fabio.jpg" />przy użyciu tych samych rekwizytów, React będzie wywoływał za renderkażdym razem, nawet jeśli wynik byłby dokładnie taki sam. Pamiętaj jednak, że React implementuje różnicowanie domen, więc DOM nie byłby w rzeczywistości aktualizowany. Mimo to wykonanie różnicowania domen może być kosztowne, więc w tym scenariuszu byłoby to marnotrawstwem.

Jeśli zamiast tego UserAvatarelement się wysuwa PureComponent, wykonuje się płytkie porównanie. A ponieważ rekwizyty i nextProps są takie same, rendernie będą w ogóle wywoływane.

Uwagi na temat definicji „czystej” w React:

Ogólnie rzecz biorąc, „czysta funkcja” jest funkcją, która zawsze ocenia ten sam wynik przy tych samych danych wejściowych. Wynik (dla React, to jest zwracane przez rendermetodę) nie zależy od historii / stanu i nie ma żadnych skutków ubocznych (operacji, które zmieniają „świat” poza funkcją).

W React komponenty bezstanowe niekoniecznie są czystymi komponentami zgodnie z powyższą definicją, jeśli nazwiesz „bezstanowy” komponent, który nigdy nie wywołuje this.setStatei nie używa this.state.

W rzeczywistości PureComponentmożesz nadal wywoływać działania niepożądane podczas metod cyklu życia. Na przykład możesz wysłać zapytanie ajax wewnątrz componentDidMountlub wykonać obliczenia DOM, aby dynamicznie dopasować wysokość div wewnątrz render.

Definicja „Głupich komponentów” ma bardziej „praktyczne” znaczenie (przynajmniej w moim rozumieniu): głupi komponent „dostaje polecenie” co robić przez komponent nadrzędny za pomocą rekwizytów i nie wie, jak to zrobić, ale używa rekwizytów zamiast tego wywołania zwrotne.

Przykład „inteligentnego” AvatarComponent:

class AvatarComponent extends Component {
    expandAvatar () {
        this.setState({ loading: true });
        sendAjaxRequest(...).then(() => {
            this.setState({ loading: false });
        });
    }        

    render () {
        <div onClick={this.expandAvatar}>
            <img src={this.props.username} />
        </div>
    }
}

Przykład „głupiego” AvatarComponent:

class AvatarComponent extends Component {
    render () {
        <div onClick={this.props.onExpandAvatar}>
            {this.props.loading && <div className="spinner" />}
            <img src={this.props.username} />
        </div>
    }
}

Na koniec powiedziałbym, że „głupi”, „bezpaństwowy” i „czysty” to całkiem różne pojęcia, które czasami mogą się nakładać, ale niekoniecznie, w zależności od twojego przypadku użycia.

fabio.sussetto
źródło
1
Naprawdę doceniam twoją odpowiedź i wiedzę, którą podzieliłeś się. Ale moje prawdziwe pytanie brzmi: kiedy powinniśmy wybrać co? . W przypadku tego samego przykładu, o którym wspomniałeś w swojej odpowiedzi, jak powinienem go zdefiniować? Czy powinien to być funkcjonalny komponent bezstanowy (jeśli tak, dlaczego?), Czy rozszerzenie PureComponent (dlaczego?), Czy rozszerzenie klasy Component (ponownie dlaczego?). Jak decydujesz, jak wybierasz pomiędzy tymi trzema w zależności od celu / rozmiaru / rekwizytów / zachowania naszych komponentów?
Yadhu Kiran,
1
Nie ma problemu. W przypadku funkcjonalnego komponentu bezstanowego istnieje lista zalet / wad, którą mogę rozważyć, czy będzie to dobre dopasowanie. Czy to odpowiada ci pierwszy punkt? Spróbuję jeszcze bardziej odpowiedzieć na pytanie wyboru.
fabio.sussetto,
2
Komponenty funkcjonalne są zawsze ponownie renderowane, gdy komponent nadrzędny jest aktualizowany, nawet jeśli w ogóle nie używają props. przykładem .
AlexM
1
To jedna z najbardziej wyczerpujących odpowiedzi, które przeczytałem od dłuższego czasu. Świetna robota. Jeden komentarz do pierwszego zdania: Podczas przedłużania PureComponentnie powinieneś implementować shouldComponentUpdate(). Powinieneś zobaczyć ostrzeżenie, jeśli faktycznie to zrobisz.
jjramos
1
Aby uzyskać rzeczywisty wzrost wydajności, powinieneś spróbować użyć PureComponentkomponentów, które mają zagnieżdżone właściwości obiektu / tablicy. Oczywiście musisz zdawać sobie sprawę z tego, co się dzieje. Jeśli dobrze rozumiem, jeśli nie mutujesz rekwizytów / stanu bezpośrednio (co React próbuje uniemożliwić robieniu ostrzeżeń) lub za pośrednictwem zewnętrznej biblioteki, powinieneś dobrze używać PureComponentzamiast Componentprawie wszędzie ... z wyjątkiem bardzo prostych komponentów, w których może faktycznie być szybszy NIE używać - patrz news.ycombinator.com/item?id=14418576
Matt Browne
28

nie jestem geniuszem zbyt reagującym, ale z mojego zrozumienia możemy korzystać z każdego elementu w następujących sytuacjach

  1. Komponent bezstanowy - są to komponenty, które nie mają cyklu życia, dlatego te komponenty powinny być używane do renderowania powtarzającego się elementu komponentu nadrzędnego, takiego jak renderowanie listy tekstowej, która po prostu wyświetla informacje i nie ma żadnych działań do wykonania.

  2. Czysty komponent - są to przedmioty, które mają cykl życia i zawsze zwracają ten sam wynik, gdy podany zostanie określony zestaw rekwizytów. Komponentów tych można używać podczas wyświetlania listy wyników lub konkretnych danych obiektowych, które nie mają złożonych elementów potomnych i używanych do wykonywania operacji, które wpływają tylko na siebie. taka wyświetlająca lista kart użytkownika lub lista kart produktów (podstawowe informacje o produkcie) i tylko działanie, które użytkownik może wykonać, to kliknąć, aby wyświetlić stronę ze szczegółami lub dodać do koszyka.

  3. Normalne komponenty lub złożone komponenty - użyłem terminu złożony komponent, ponieważ zwykle są to komponenty na poziomie strony i składają się z wielu komponentów potomnych, a ponieważ każde dziecko może zachowywać się na swój własny, unikalny sposób, więc nie możesz być w 100% pewien, że renderują ten sam wynik dla danego stanu. Jak powiedziałem zwykle powinny być używane jako elementy kontenera

abhirathore2006
źródło
1
Takie podejście może działać, ale możesz stracić duże korzyści. Używanie PureComponentw komponentach na poziomie root i komponentach w górnej części hierarchii jest zwykle miejscem, w którym można zaobserwować największy wzrost wydajności. Oczywiście musisz unikać mutowania rekwizytów i informować bezpośrednio, aby czyste komponenty działały poprawnie, ale bezpośrednie mutowanie obiektów w każdym razie jest anty-wzorem w React.
Matt Browne
5
  • React.Componentjest domyślnym „normalnym” komponentem. Deklarujesz je za pomocą classsłowa kluczowego i extends React.Component. Pomyśl o nich jak o klasie z metodami cyklu życia, procedurami obsługi zdarzeń i dowolnymi metodami.

  • React.PureComponentto React.Componentimplementuje shouldComponentUpdate()funkcję, która dokonuje płytkiego porównania jej propsi state. Musisz użyć, forceUpdate()jeśli wiesz, że komponent ma rekwizyty lub zagnieżdżone dane, które uległy zmianie i chcesz ponownie renderować. Nie są więc świetne, jeśli potrzebujesz komponentów do ponownego renderowania, gdy tablice lub obiekty przekazywane jako rekwizyty lub ustawione w stanie zmieniają się.

  • Komponenty funkcjonalne to takie, które nie mają funkcji cyklu życia. Są podobno bezpaństwowce, ale są tak ładne i czyste, że teraz mamy haczyki (od React 16.8), dzięki czemu możesz nadal mieć stan. Sądzę więc, że są to po prostu „czyste komponenty”.

JackyJohnson
źródło