Jak bezpečně používat kontext React

Kontext je v programu React velmi výkonný prvek a kolem něj je spousta vyloučení odpovědnosti. Trochu jako zakázané ovoce v ráji.

To by mělo stačit, aby vás udržovalo daleko od kontextu? Samozřejmě ne, je to (nepodporovaná) funkce React a zakázané funkce budou použity pro pouhou skutečnost, že existují! Kontext umožňuje předávat data komponentám hluboko ve stromu komponent, aniž by bylo nutné o nich vědět prostřední komponenty. Klasickými případy použití pro kontext jsou tématika, lokalizace a směrování.

Dan Abramov vymyslel některá moudrá pravidla, když nechat tento drahokam viset na stromě:

Nyní jste pravděpodobně následovali tuto moudrou radu, ale mezitím byste mohli pomocí knihoven, které používají kontext, například reaktivátor, stále dostat do potíží, když se kombinují s jinými knihovnami, jako je reakt-redux nebo mobx-reagovat, nebo dokonce i když v kombinaci s vlastním shouldComponentUpdate nebo s programem React.PureComponent. Dlouhodobé problémy lze nalézt v nástroji Sledování problémů React a v nástroji Sledování problémů knihoven souvisejících s reakcí.

Proč je tedy pro vás tento blog relevantní? No buď proto

  1. Jste autor knihovny
  2. Používáte knihovnu, která používá kontext, nebo používáte kontext sami a chcete bezpečně používat shouldComponentUpdate (SCU) nebo jeho implementace (např. PureComponent, Redux connect nebo MobX pozorovatel).

Proč je program Context + shouldComponentUpdate problematický?

Kontext se používá ke komunikaci s hluboce obsaženými komponenty. Například kořenová komponenta definuje téma a jakákoli komponenta ve stromu komponent by se mohla (nebo nemusí) zajímat o tyto informace. Jako v oficiálním příkladu.

shouldComponentUpdate (SCU) na druhé straně zkratuje opakované vykreslení části stromu komponent (včetně dětí), například pokud rekvizity nebo stav komponenty nejsou smysluplně upraveny. Pokud to součást umí říct. Ale to by mohlo náhodně blokovat šíření kontextu ...

Představme si tento problém s rušením v jednoduché aplikaci:

Problematická koordinace mezi kontextem a SCU je jasně viditelná, jakmile stisknete tlačítko „Červené prosím!“ (Na kartě „Výsledek“ výše). Tlačítko samotné získá svěží barvu, ale položky úkolů se neaktualizují. Důvod je jednoduchý: naše komponenta TodoList je inteligentní; ví, že pokaždé, když neobdrží žádné nové úkoly, nemusí se znovu vykreslovat. (Inteligence je dosaženo zděděním od PureComponent, který implementuje shouldComponentUpdate).

Díky této chytrosti (což je nezbytné pro udržení React performant ve velkých aplikacích) komponenty ThemedText uvnitř TodoListu neobdrží nový kontext s aktualizovanou barvou! Protože SCU vrací false, není aktualizován TodoList ani žádný z jeho potomků.

Ještě horší je, že nemůžeme implementovat SCU v TodoList ručně takovým způsobem, že je to opraveno, protože SCU neobdrží relevantní kontextová data (barva), protože není (a neměla by!) Přihlášena k odběru tohoto konkrétního kusu kontextových dat. Konec konců to není samoúčelná součást.

Abychom to shrnuli, shouldComponentUpdate vracející false způsobí, že aktualizace kontextu již nebude šířena na podřízené komponenty. Docela špatné, co? Můžeme to opravit?

ShouldComponentUpdate a Context mohou spolupracovat!

Všimli jste si, že k problému došlo až po aktualizaci kontextu? To je také klíč k řešení problému. Jen se ujistěte, že nikdy neaktualizujete kontext. Jinými slovy:

  1. Kontext by se neměl měnit; mělo by být (mělké) neměnné
  2. Komponenty by měly dostávat kontext pouze jednou; když jsou postaveny.
Nebo, jinak řečeno, neměli bychom stav ukládat přímo v našem kontextu. Namísto toho bychom měli používat kontext jako systém vstřikování závislosti.

To znamená, že SCU již nebude zasahovat do kontextu, který je třeba předat, protože nikdy nemusí předávat svým dětem nový kontext. Skvělý! To řeší všechny naše problémy!

Komunikace změn pomocí kontextového závislostního vstřikování

Kromě toho, co .. co když chceme změnit barvu našeho tématu? Je to jednoduché, máme zavedený systém závislosti závislostí (DI), takže můžeme předat obchod, který řídí naše téma, a přihlásit se k odběru. Nikdy nepředáváme nový obchod, ale pouze se ujistěte, že je obchod sám o sobě a může informovat komponenty o změnách:

Nebo kompletní spustitelný výpis:

Tento příklad nyní správně reaguje na změnu barvy. Přesto stále používá PureComponent. Také API důležitých komponent App, TodoList a ThemedText se nezměnilo.

Naše implementace ThemeProvider se však stala složitější. Vytváří objekt Téma, který udržuje stav našeho tématu. Téma je také emitorem událostí chudého člověka. To umožňuje součástem, jako je ThemedText, přihlásit se k odběru budoucích změn. Objekt Theme je předán skrz strom komponent komponenty ThemeProvider. K tomu se stále používá kontext, ale po počátečním průchodu kontext již není relevantní, protože budoucí aktualizace jsou šířeny samotným tématem namísto vytváření nového kontextu.

Tato implementace je trochu příliš zjednodušená. Řádná implementace by také musela vyčistit posluchače událostí v komponentěWillUnmount a pravděpodobně by měla používat setState místo forceUpdate. Dobrou zprávou však je, že se jedná pouze o knihovnu, kterou používáte / stavíte. To nemá vliv na spotřebitele knihovny. Neočekávaná implementace shouldComponentUpdate v prostřední komponentě již knihovnu nezruší.

Závěr

Omezením použití kontextu, aby byl pouhým systémem závislostní injekce namísto kontejneru stavu, můžeme učinit obě kontextové knihovny a shouldComponentUpdate se chovat správně bez rušení a bez porušení API spotřebitelů. A velmi důležité, funguje to v rámci současných omezení Reactova kontextu. Jen se držte tohoto jednoduchého pravidla:

Kontext by měl být použit, jako by byl přijat pouze jednou každou komponentou.

Poslední připomínka: Kontext je stále experimentální funkcí a vy byste se měli vyhnout přímému použití kontextu (viz pravidla Dan Abramova výše). Místo toho použijte knihovny, které se abstraktují v kontextu (příklady viz níže). Ale pokud jste autorem knihovny nebo pokud píšete pěkné komponenty vyššího řádu, abyste se vypořádali s kontextem, držením se výše uvedeného řešení se vyhnete některým nepříjemným překvapením.

Aktualizace 29–9–2016: Ryan Florence právě zveřejnil obecný balíček, který využívá tohoto vzoru, takže nemusíte psát všechny kotle sami: Reakce-kontext-emise

Bonus: Použití pozorovatelných MobX jako kontextu zjednodušuje věci

(Tato část je zajímavá zejména pokud používáte nebo máte zájem o MobX)

Pokud náhodou používáte MobX, můžete ve skutečnosti přeskočit celou záležitost emitoru událostí a namísto toho uložit (v rámečku) pozorovatelné v kontextu a přihlásit se k odběru pomocí komponenty pro dekoraci pozorovatelů / vyššího řádu. Tím se odstraní potřeba spravovat předplatné dat sami:

Ve skutečnosti je to ještě jednodušší pomocí mechanismu Poskytovatel / Vstřik, což je malá abstrakce přes mechanismus kontextu React, zabudovaný do MobX. Odstraňuje kontextové prohlášení deklarující contextTypes a podobné věci. Všimněte si, že podobné koncepty lze nalézt ve zobecněných libsech, jako je recompose nebo rea-tunnel.

Za co to stojí; Všimněte si, že ačkoli naše počáteční řešení založené na DI bylo 1,5krát delší než původní kódová základna, toto konečné řešení je stejně dlouhé jako původní problematická implementace.