Потопете се в контейнерите на React
В първата статия разгледахме маршрути и гледки. В тази част ще представим нова концепция, при която компонентите не създават изгледи, а улесняват нещата за съществуващите. Както винаги, статията придружава кода на GitHub, ако желаете, можете веднага да се обърнете към него.
Също така ще добавим данни към нашето приложение. Ако сте запознати с всякакъв вид дизайн на компоненти или MVC модели, тогава вероятно знаете, че смесването на изгледи и поведение на приложение в един и същ компонент се счита за лоша практика. С други думи, изгледите се нуждаят само от данни за изобразяване, не е необходимо да знаят нищо за това откъде идват тези данни, как са създадени и модифицирани.
Получаване на данни с Ajax
Като пример за лоша практика, нека разширим компонента UserList от предишната статия за обработка и извличане на данни:
Ако имате нужда от по-подробно описание на това какво прави този компонент, вижте обяснението в GitHub.
Какво не е наред с този пример? Да започнем с това, че нарушава правилото за смесване на „поведение“ и „преглед на изход“ - тези две неща трябва да бъдат разделени.
Няма нищо лошо в използването на getInitialState за инициализиране на състоянието на компонент и няма нищо лошо в Ajax заявка от componentDidMount (въпреки че трябва да се абстрахира, така че да може да извиква и други функции). Проблемът е, че го правим заедно в един компонент, съдържащ изгледа. Това твърдо свързване прави приложението по-малко гъвкаво и нарушава принципа DRY. Но какво ще стане, ако трябва да извлечете списъка с потребители от някъде другаде? Извличането на потребителския списък е обвързано с изглед, така че компонентът не може да се използва повторно.
Второпроблемът е, че jQuery се използва за Ajax заявката. Има много добри неща в jQuery, но в повечето случаи е свързано с рендиране на DOM и React има свой собствен подход към това. За неща, които не са свързани с DOM като Ajax, има много по-специализирани алтернативи.
Една такава алтернатива е Axios, Ajax инструмент, базиран на обещания. Колко си приличат?
Във всички останали примери ще продължим да използваме Axios. Но има и други подобни инструменти: got, fetch и SuperAgent.
Имоти и статус
Преди да преминем към контейнери и презентационни компоненти, трябва да разберем свойствата и състоянието.
Свойствата и състоянието са свързани в смисъл, че те са „модел“ (данни) за компонентите на React. И двете могат да се предават от родителски към дъщерни компоненти. Въпреки това, както свойствата, така и състоянието на родителския контейнер стават само свойства на дъщерните компоненти.
Помислете за пример: ComponentA предава някои от свойствата и състоянието на дъщерния ComponentB. Методът за изобразяване на ComponentA изглежда по следния начин:
Въпреки че foo е състояние на родителския компонент, той става свойство на дъщерния компонент. Атрибутът bar също става свойство на дъщерния компонент, тъй като всички данни, предадени от родителския компонент към децата, стават техни свойства. Следващият пример показва как методът ComponentB осъществява достъп до foo и bar като свойства:
В примера за получаване на данни с Ajax, данните, получени от заявката на Ajax, станаха състоянието на компонента. В примера този компонент няма деца, но ако имаше, състоянието щеше да тече към тях като собственост.
За да разберем по-добре понятието държавапрочетете документацията на React. И в нашата статия данните, които се променят с времето, ще бъдат наричани „състояние“.
Разделяне на компонент
В примера с Ajax създадохме проблем. Нашият компонент UserList работи, но иска да направи твърде много. За да разрешим този проблем, ще го разделим на два компонента, всеки от които ще играе различна роля. Тези два вида компоненти се наричат контейнерни компоненти и презентационни компоненти, наричани също съответно „умни“ и „глупави“ компоненти.
Накратко, компонентите на контейнера съдържат изходни данни и работят според състоянието. Състоянието се предава на презентационните компоненти като свойство и след това се изобразява на изгледа.
Термините "умни" и "глупави" компоненти бавно излизат от употреба в общността. Просто ги споменавам, за да нямате въпроси, когато четете стари статии, сега те са известни като контейнерни компоненти и презентационни компоненти.
Компоненти на презентацията
Може би не знаете това, но вече сте видели презентационния компонент в нашата серия. Само си представете как е изглеждал компонентът UserList, преди да започне да управлява състоянието си.
Не е точно това, което беше, но е презентационен компонент. Голямата разлика между него и оригинала е, че новият компонент итерира данните, за да създаде елементи от списъка и приема потребителските данни като свойства.
Презентационните компоненти са "глупави" в смисъл, че нямат представа откъде идват свойствата, с които работят. държава? Не, нямаме.
Презентационните компоненти никога не трябва да променят данните в свойствата сами по себе си. Всъщност всеки компонент, който приема свойства, трябва да вземе предвидче данните са неизменни и принадлежат на своя родител. Въпреки че не засяга по никакъв начин стойността на данните в свойството, можете свободно да форматирате данните за показване в изгледа (например чрез преобразуване на времево клеймо на Unix в нещо по-четливо).
В React събитията се прикачват директно към изгледа с помощта на атрибути като onClick. И някои може да се изненадат как събитията работят с презентационен компонент, който не е предназначен да променя свойствата. Това е темата на следващия раздел.
Когато създавате DOM възли в цикъл, ключовият атрибут на елемента трябва да бъде уникален (спрямо съседите). Това се отнася само за DOM възли от най-високо ниво - в нашия случай.
Освен това, ако вложеното връщане ви изглежда странно, помислете за друг подход, който прави същото, като извлича кода, за да създаде елемент от списъка в отделна функция.
Контейнерни компоненти
Контейнерните компоненти почти винаги са родител на презентационни компоненти. До известна степен те служат като посредници между презентационните компоненти и останалата част от приложението. Те се наричат още "интелигентни" компоненти, защото са наясно с приложението като цяло.
За да направим разлика между контейнера и презентационния компонент, трябва да използваме различни имена, така че да няма объркване, контейнерът ще се нарича UserListContainer:
За краткост в нашите примери са пропуснати декларациите require() и module.exports. Но в този случай е важно да се покаже, че презентационните компоненти в контейнерите са посочени като директни зависимости, така че този пример показва целия код за пълнота.
Контейнерните компоненти могат да бъдат създадени точно като всеки друг React компонент. Те също, както и останалите компоненти, иматrender метод, но те не създават нищо за своето изобразяване, а вместо това връщат резултата като презентационен компонент.
Бърза бележка относно функциите със стрелки на ES6 : Може би сте забелязали, че класическата var _this = този трик е необходима за този пример. Функциите със стрелки ES6 имат не само по-сбит синтаксис, но и други предимства, които ви позволяват да се справите без такъв трик. Тази поредица от статии обикновено използва стария синтаксис на ES5, така че можете да се съсредоточите върху React, но урокът на GitHub използва силно ES6, с обяснения във файловете README.
Нека започнем с добавяне на събитие към презентационния компонент ( , можете да щракнете върху него) директно, за да стигнем до дъното на проблема:
Технически работи, но е лоша идея. Има шансове, че събитието ще трябва да промени данните и променените данни трябва да се съхраняват като състояние - с което презентационният компонент не трябва да има нищо общо.
В нашия пример променливото състояние ще бъде „активността“ на потребителя, но може да бъде всяка функция, която искате да свържете към onClick.
Най-доброто решение би било да прехвърлите функционалността от контейнера към презентационния bean като свойство, както в примера:
Атрибутът onClick трябва да бъде на изгледа, т.е. на презентационния компонент. Въпреки това функцията, която го извиква, е преместена в компонента на родителския контейнер. Това е по-добре, тъй като контейнерът има състояние.
Ако родителската функция промени състоянието, тогава промяната на състоянието ще задейства ново изобразяване на родителската функция, което ще актуализира дъщерния компонент. В React всичко това се прави автоматично.
Ето демонстрация, показваща как събитията на контейнера променят състоянието, което еавтоматично актуализира презентационния компонент:
Имайте предвид, че този пример работи с неизменни данни и използва метода .bind().
Използване на контейнери с рутер
Рутерът вече не трябва да използва директно UserList. Вместо това рутерът използва UserListContainer, който ще използва UserList. В крайна сметка UserListContainer връща UserList и рутерът ще получи това, от което се нуждае.
Оператор за поток и разпространение на данни
Концепцията за реквизитите, преминаващи от родител към дете, се нарича поток в React. Досега нашите примери показват само прости връзки родител-дете, но реално приложение може да има много вложени компоненти. Представете си потока от данни от родителски компоненти на високо ниво до множество дъщерни компоненти чрез състояние и свойства. Това е основна концепция в React и е важно да я имате предвид, преди да преминете към следващата част на Redux.
ES6 има оператор за разпространение, който е много полезен. React използва синтаксис, подобен на JSX и наистина помага при предаването на props. Ръководството от GitHub също го използва, не забравяйте да прочетете документацията за него.
Функционални компоненти без състояние
React версия 0.14 (издадена в края на 2015 г.) има нова възможност за много по-лесно създаване на компоненти без състояние (презентационни компоненти). Те се наричат функционални компоненти без състояние.
Може би вече сте забелязали, че след като контейнерът и презентационните компоненти са разделени, презентационните компоненти остават с един метод за изобразяване. В такива случаи React ви позволява да напишете компонента катоедна функция:
Очевидно новият начин е много по-лаконичен. Но не забравяйте, че работи само за компоненти с един метод за изобразяване.
С новите функционални компоненти без състояние, функцията приема аргумент със свойства, което означава, че нямате нужда от това за достъп до свойства.
Както може би сте забелязали, React не изглежда като традиционния MVC. React често се описва като „просто слой за изглед“. Проблемът с това твърдение е, че е твърде лесно за начинаещите да повярват, че React трябва да се впише в техните традиционни идеи за MVC, което обикновено означава да може да се използва с традиционни контролери и модели от библиотеки на трети страни.
Въпреки че е вярно, че в React няма „традиционни контролери“, той има свои собствени средства за разделяне на изгледи и поведение. Вярвам, че контейнерните компоненти служат на същата основна цел като традиционните MVC контролери.
Що се отнася до моделите, виждал съм хора да използват Backbone модели с React и съм сигурен, че имат различни мнения за това колко добре се вписват заедно. Но не съм сигурен дали традиционните модели си струва да се използват в React. Начинът, по който потокът от данни работи в React, не е същият като при конвенционалните модели. Създаден от Facebook, шаблонът за дизайн на Flux е начин за свързване на вродената способност на React да предава данни. В следващата част ще разгледаме Redux, много популярна реализация на Flux, която виждам като алтернатива на традиционните модели.
Заключение
Контейнерните компоненти са по-скоро концептуално, отколкото точно решение. Примерите в тази статия показват само един начин за създаването им. Въпреки че тази концепция вече е пуснала корени, която дори се използва официално от разработчиците във facebook, въпреки че теможе да използва друга терминология.