Как безопасно да използвате контекста React

Контекстът е много мощна функция в React, с много отказ от отговорност. Малко като забранения плод в рая.

Това трябва да е достатъчно, за да ви държи далеч от контекста нали? Е, разбира се, не, това е (неподдържана) функция React, а забранените функции ще бъдат използвани само за факта, че съществуват! Контекст дава възможност да се предават данни на компоненти дълбоко в дървото на компонентите, без да се нуждаят от междинни компоненти, за да знаят за това. Класическите случаи на употреба за контекст са тематизиране, локализиране и маршрутизиране.

Дан Абрамов е измислил няколко мъдри правила кога да оставим този скъпоценен камък да виси в дървото:

Вероятно сте следвали този мъдър съвет, но междувременно използването на библиотеки, които използват контекст, например react-router, все пак може да ви създаде проблеми, когато се комбинирате с други библиотеки като react-redux или mobx-react, или дори когато комбинирайки се със собствения си ShouldComponentUpdate или този, предоставен от React.PureComponent. Дълготрайни проблеми могат да бъдат намерени в проследяващия проблем на React и в проследяващите издания на библиотеки, свързани с реакцията.

И така, защо този блог е подходящ за вас? Ами защото

  1. Вие сте автор на библиотеката
  2. Използвате библиотека, която използва контекст или използвате сами контекст, и искате безопасно да използвате, ако трябваComponentUpdate (SCU) или техни реализации (напр. PureComponent, Redux connect или MobX наблюдател).

Защо Context + ShouldComponentUpdate е проблематичен?

Контекстът се използва за комуникация с дълбоко съдържащи се компоненти. Например, корен компонент дефинира тема и всеки компонент в компонентното дърво може (или не може) да се интересува от тази информация. Както в официалния пример на контекста.

от друга страна, трябва да има късо съединение за прекъсване на рендеринга на част от дървото на компонентите (включително децата), например, ако реквизитите или състоянието на компонент не са променени по смислен начин. Доколкото компонентът може да каже. Но това може случайно да блокира разпространението на контекста ...

Нека да демонстрираме този проблем с намесата в обикновено приложение:

Проблемната координация между контекст и SCU е ясно видима, след като натиснете бутона „Червено, моля!“ (В раздела „Резултат“ по-горе). Самият бутон придобива свеж цвят, но елементите на todo не се актуализират. Причината за това е проста: нашият компонент TodoList е умен; той знае, че всеки път, когато не получава нови елементи на todo, няма нужда да го прави повторно. (Умността се постига чрез наследяване от PureComponent, който внедрява трябваComponentUpdate).

Поради тази интелигентност (която е от съществено значение за поддържане на React представител при големи приложения) компонентите на ThemedText вътре в TodoList не получават новия контекст с актуализиран цвят! Тъй като SCU връща невярно, нито TodoList, нито някой от неговите потомци не се актуализира.

Още по-лошото е, че не можем да внедряваме SCU в TodoList ръчно по такъв начин, че това да е фиксирано, тъй като SCU няма да получава съответните контекстни данни (цвят), тъй като не е (и не трябва!) Да бъде абониран за това конкретно парче на контекстни данни. В крайна сметка това не е тема, познаваща темата.

За да обобщим, ако връщането на falseComponentUpdate невярно причинява актуализация на контекста да не се разпространява в дочерните компоненти. Доста лошо, а? Можем ли да поправим това?

ShouldComponentUpdate и Context могат да работят заедно!

Забелязахте ли, че проблемът се е появил само след като обновихме контекста? В това се състои и решението на проблема. Просто се уверете, че никога не актуализирате контекста. С други думи:

  1. Контекстът не трябва да се променя; тя трябва да бъде (плитка) неизменна
  2. Компонентите трябва да получават контекст само веднъж; когато са конструирани.
Или, казано по-различно, не трябва да съхраняваме състояние директно в нашия контекст. Вместо това трябва да използваме контекста като система за инжектиране на зависимост.

Това означава, че SCU вече няма да се намесва в контекста, който трябва да бъде предаден, защото никога не трябва да предава нов контекст на децата си. Страхотно! Това решава всички наши проблеми!

Комуникация на промените чрез базирано на контекста инжектиране на зависимостта

С изключение на, ами .. какво ще стане, ако искаме да променим цвета на нашата тема? Ами просто, ние имаме система за инжектиране на зависимост (DI) на място, така че можем да предадем магазин, който управлява нашата тема и да се абонирате за нея. Никога не предаваме нов магазин наоколо, но просто се уверете, че самият магазин е състоятелен и може да уведомява компоненти за промени:

Или пълният списък за изпълнение:

Забележете, че този пример реагира правилно на промяната на цвета. И все пак, той все още използва PureComponent. Също така API на важните компоненти App, TodoList и ThemedText не се промениха.

Нашата реализация на ThemeProvider обаче стана по-сложна. Той създава Тематичен обект, който поддържа състоянието на нашата тема. Темата също е (емитер на събитията на бедния човек). Това дава възможност на компоненти като ThemedText да се абонират за бъдещи промени. Обектът Theme се предава през компонентното дърво от ThemeProvider. Контекстът все още се използва за това, но след първоначалния пропуск контекстът вече не е от значение, тъй като бъдещите актуализации се разпространяват от самата Тема, вместо чрез създаване на нов контекст.

Това изпълнение е малко прекалено опростено. Правилното изпълнение също ще трябва да изчисти слушателите на събития в компонентWillUnmount и вероятно трябва да използва setState вместо forceUpdate. Но добрата новина е, че това е чисто грижа за библиотеката, която използвате / изграждате. Това не засяга потребителите на библиотеката. Неочакваното внедряване на mustComponentUpdate в междинен компонент вече няма да прекъсне библиотеката.

заключение

Като ограничаваме използването на контекста да бъде само система за инжектиране на зависимост вместо контейнер за състояния, можем да накараме както библиотеките, базирани на контекста, така и би трябвалоComponentUpdate да се държат правилно без намеса и без да нарушават API-тата на потребителите. И, много важно, тя работи в рамките на текущите ограничения на контекстната система на React. Просто се придържайте към това просто правило:

Контекстът трябва да се използва така, сякаш е получен само веднъж от всеки компонент.

Последно напомняне: Контекстът все още е експериментална функция и трябва да избягвате да използвате директно контекста (вижте правилата на Дан Абрамов по-горе). Вместо това използвайте библиотеки, които се абстрактни спрямо контекста (вижте по-долу за някои примери). Но ако сте автор на библиотеката или пишете хубави компоненти от по-висок ред, за да се справите с контекста, придържането към горното решение ще избегне някои гадни изненади.

Актуализация 29–9–2016: Райън Флорънс току-що публикува общ пакет, който се възползва от този модел, така че не е нужно да напишете сами всички кодове: реагирайте-контекст-емисия

Бонус: Използването на MobX наблюдаеми като контекст опростява нещата

(този раздел е главно интересен, ако използвате или се интересувате от MobX)

Ако случайно използвате MobX, всъщност можете да пропуснете целия материал за излъчване на събития и вместо това да съхранявате (в полето) наблюденията в контекста и да се абонирате за тях, като използвате декоратор за наблюдение / компонент с по-висок ред. Това премахва необходимостта сами да управлявате абонаментите за данни:

Всъщност човек може да го направи още по-прост, като използва доставчика / инжектиращия механизъм, който представлява малка абстракция върху контекстния механизъм на React, вграден в MobX. Той премахва котлона за деклариране на контекстни типове и подобни неща. Обърнете внимание, че подобни понятия могат да се намерят в генерализирани либри като преразказване или реагиране на тунел.

За какво си струва; имайте предвид, че въпреки че първоначалното ни решение на базата на DI е било 1,5 пъти по-дълго от оригиналната кодова база, това окончателно решение е толкова дълго, колкото оригиналното проблематично изпълнение.