Jak zacházet se státem v React. Chybějící FAQ.

Motivace

Nedávno se v Reactu hodně debatovalo o tom, jak řídit stát. Někteří tvrdí, že setState () nefunguje podle očekávání. Že musíte stav externalizovat pomocí stavového kontejneru typu Flux a zcela se vyhnout stavu komponenty.

Na druhé straně jsou lidé znepokojeni tím, že by se tyto mylné představy mohly stát dogmou:

Přidání externího stavového kontejneru jen pro zábavu nebo proto, že vám některé návody říkají, jak to udělat, zní jako, není to dobré technické rozhodovací kritérium.

Abych si to ujasnil, není nic špatného na Reduxu, MobXu nebo jakémkoli externím stavovém kontejneru, na který si vzpomenete. Ve skutečnosti jsou velmi užitečné a přicházejí s širokým ekosystémem, který vyhovuje vašim potřebám. Jde ale o to, že je nemusíte vůbec, nebo alespoň ještě.

Pokud se učíte reagovat od nuly, vytvořil Pete Hunt tu nejlepší radu, kterou můžete získat. Dokonce i tvůrce hlavního člena týmu Redux a Reacta Dan Abramov doporučuje:

To znamená, že musíte pochopit, jak zacházet se státem způsobem React, než přemýšlíte o Flux.

V případě, že spouštíte aplikaci ve skutečném životě, můžete toho dosáhnout bez Flux. Rozdělení součástí do dvou kategorií: kontejnery a prezentace. Tímto způsobem získáte udržovatelnost a opětovné použití. Také v případě, že budete muset Flux zavést později, bude migrační cesta čistší. Poskytuje vám možnost učinit rozhodnutí v poslední zodpovědné chvíli.

I když už používáte Flux, existují případy, kdy byste měli použít stav komponenty. Pomyslete na knihovnu komponent, kterou chcete sdílet mezi projekty, kde jsou všechny komponenty samostatné a nezávislé. Opravdu nechcete mít státní kontejner jako závislost.

Mám na mysli:

  • V komunitě existují zkřížené názory, mylné představy a nedostatek znalostí o tom, jak řídit stát v Reactu.
  • Aby bylo možné využít veškerou sílu, kterou vám React dává, je zásadní důkladné pochopení toho, jak zvládat stav.
  • Pokud to nepotřebujete, nepřidávejte do své aplikace další vrstvu složitosti. Pamatujte: jednoduchost záleží.

Účelem následující části FAQ je zmírnit složitosti stavu manipulace v React.

Často kladené otázky.

Jak funguje stát?

Komponenta React je jako stavový stroj, který představuje uživatelské rozhraní. Každá akce uživatele potenciálně vyvolá změnu v tomto stavu stroje. Poté je nový stav reprezentován jiným prvkem React.

React uloží stav komponenty v tomto.státu. Počáteční hodnotu this.state můžete nastavit dvěma různými způsoby. Každý z nich odpovídá způsobu vytvoření komponenty:

// Používání React.createClass
var Counter = React.createClass ({
    getInitialState: function () {
        return {counter: 0};
    },
    ...
});
// Používání tříd ES6
třída Počítadlo rozšiřuje React.Component {
    konstruktor (rekvizity) {
        super (rekvizity);
        this.state = {counter: 0};
    }
    ...
}

Stav komponenty lze změnit voláním:

this.setState (data, callback);

Tato metoda provádí mělké sloučení dat do this.state a znovu vykreslí komponentu. Argument data může být objekt nebo funkce, která vrací objekt obsahující klíče k aktualizaci. Volitelné zpětné volání - pokud je k dispozici - bude vyvoláno po dokončení opětovného vykreslení komponenty. Toto zpětné volání budete zřídka potřebovat, protože React se postará o udržování aktuálního uživatelského rozhraní.

Pojďme se podívat na příklad:

Co si mám ponechat ve stavu React?

Dan Abramov odpověděl na tuto otázku jedním tweetem:

V zásadě říká, že nedržet stav vypočítaný z rekvizit, ani stav, který není použit v metodě render (). Příklad:

// Nekopírujte data z rekvizit ve stavu
// Antipattern
třída Component rozšiřuje React.Component {
    konstruktor (rekvizity) {
        super (rekvizity);
        this.state = {message: props.message};
    }
    
    poskytnout() {
        return 
{this.state.message}
;     } }

Problém s výše uvedeným příkladem je, že nastaví stav pouze při prvním vytvoření komponenty. Když přijdou nové rekvizity, stav zůstane stejný, takže uživatelské rozhraní se neaktualizuje. Poté musíte aktualizovat stav v komponentěWillReceiveProps (), což vede ke zdvojení zdroje pravdy. Lepší je jen tím, že se vyhnete této situaci:

// Lepší příklad
třída Component rozšiřuje React.Component {
    poskytnout() {
        return 
{this.props.message}
;     } }

Totéž platí, když držíte stav na základě výpočtu rekvizit:

// Nepodržujte stav založený na výpočtu rekvizit
// Antipattern
třída Component rozšiřuje React.Component {
    konstruktor (rekvizity) {
        super (rekvizity);
        this.state = {fullName: `$ {props.name} $ {props.lastName}`};
    }
    
    poskytnout() {
        return 
{this.state.fullName}
;     } }
// Lepší přístup
třída Component rozšiřuje React.Component {
    poskytnout() {
        const {name, lastName} = this.props;
        return 
{`$ {name} $ {lastName}`}
;     } }

Přestože není nic špatného s nastavením počátečního stavu založeného na rekvizitách, pokud dáte jasně najevo, že jde pouze o data semen:

// Nejedná se o antipattern
třída Component rozšiřuje React.Component {
    konstruktor (rekvizity) {
        super (rekvizity);
        this.state = {count: props.initialCount};
        this.onClick = this.onClick.bind (this);
    }
    
    poskytnout() {
        return 
{this.state.count}
;     }          při kliknutí() {         this.setState ({count: this.state.count + 1});     } }

V neposlední řadě:

// Nepřidržujte stav, který pro vykreslování nepoužíváte.
// Vede k nepotřebným opakovaným vykreslením a dalším nesrovnalostem.
// Antipattern
třída Component rozšiřuje React.Component {
    konstruktor (rekvizity) {
        super (rekvizity);
        this.state = {count: 0};
    }
    
    poskytnout() {
        return 
{this.state.count}
;     }          componentDidMount () {         const interval = setInterval (() => (             this.setState ({count: this.state.count + 1})         ), 1000);
        this.setState ({interval});
    }
    componentWillUnmount () {
        clearInterval (this.state.interval);
    }
}
// Lepší přístup
třída Component rozšiřuje React.Component {
    konstruktor (rekvizity) {
        super (rekvizity);
        this.state = {count: 0};
    }
    
    poskytnout() {
        return 
{this.state.count}
;     }          componentDidMount () {         this._interval = setInterval (() => (             this.setState ({count: this.state.count + 1})         ), 1000);     }
    componentWillUnmount () {
        clearInterval (this._interval);
    }
}

Je pravda, že setState () je asynchronní?

Krátká odpověď: Ano.

V zásadě, když vyvoláte setState () React naplánuje aktualizaci, jsou výpočty zpožděny, dokud to není nutné. Dokumentace React je o tom trochu zavádějící:

Tyto dvě věty jsou zjevně v rozporu. Pojďme trochu experimentovat a uvidíme, co se stane:

třída Component rozšiřuje React.Component {
    konstruktor (rekvizity) {
        super (rekvizity);
        this.state = {count: 0};
        this.onClick = this.onClick.bind (this);
    }
    
    poskytnout() {
        return 
{this.state.count}
;     }          při kliknutí() {         this.setState ({count: this.state.count + 1});         console.log (this.state.count);     } }

Když tuto komponentu vykreslíte a budete s ní interagovat, uvidíte, že hodnoty zobrazené v konzole jsou hodnoty z předchozího stavu. Je to proto, že React vlastní událost a ví dost na to, aby mohla aktualizaci aktualizovat. Výsledek: asynchronní stavová mutace.

Co se však stane, když událost pochází z externího zdroje?

// Volání setState () dvakrát ve stejném kontextu provádění je špatná // praxe. Používá se zde pro ilustraci. Místo toho použijte // atomovou aktualizaci v reálném kódu
třída Component rozšiřuje React.Component {
    konstruktor (rekvizity) {
        super (rekvizity);
        this.state = {count: 0};
    }
    
    poskytnout() {
        return 
{this.state.count}
;     }          componentDidMount () {         this._interval = setInterval (() => {             this.setState ({count: this.state.count + 1});             console.log (this.state.count);             this.setState ({count: this.state.count + 1});             console.log (this.state.count);         }, 1000);     }
componentWillUnmount () {
        clearInterval (this._interval);
    }
}

Snap! Vrátí existující hodnotu, jak je navržena v dokumentaci, a to i ve dvojím volání setState () ve stejném kontextu provádění. Je to proto, že React neví dost na to, aby aktualizoval dávku a musí aktualizovat stav co nejdříve.

To je složité, navrhuji, aby se setState () vždy považoval za asynchronní a vyhnete se problémům.

Slyšel jsem, že volání setState () za určitých okolností nespustí opakované vykreslení. Jaké jsou tyto okolnosti?

  1. Když zavoláte setState () v rámci komponentyWillMount () nebo componentWillRecieveProps (), nespustí se žádné další opakované vykreslení. Reagovat šarže aktualizace.
  2. Když vrátíte false od shouldComponentUpdate (). Tímto způsobem je metoda render () přeskočena společně s komponentou komponentyWillUpdate () a componentDidUpdate ().
Poznámka:
Pokud chcete kopat více do životního cyklu komponenty, už jsem do něj napsal příspěvek.

Závěry

Správné zacházení se státem v React může být výzvou. Doufám, že už máte jasnější obrázek.

Máte-li dotaz, který nebyl zodpovězen, neváhejte se zeptat v komentářích nebo prostřednictvím Twitteru. Rádi vám poskytneme zpětnou vazbu a otázku zahrneme do části Nejčastější dotazy.

Doufám, že tento příspěvek pomohl prohloubit vaše znalosti o React. Pokud ano, doporučujeme jej oslovit více lidí.