Как да опростим състоянието във вашето приложение React - Redux с обрат

Снимка на Труман Адриан Лобато Де Фария на Unsplash

Нов, много по-лесен синтаксис и семантика за добрия стар Redux

Думите "прост" и "Redux" рядко се появяват заедно в едно изречение. И въпреки това, голяма част от общността React е приела Redux като едно от най-добрите решения за прилагане на състоянието на приложение.

Сега има начин да използвате Redux, дори ако не напишете един ред от кода на котела на Redux. Не е нужно дори да знаете или да научите Redux. Докато сте убедени, че Redux е най-добрият избор за държавните изисквания на приложението ви, ще искате да го прочетете.

В тази статия ще разгледаме следните теми:

  • Управление на прости промени в състоянието на приложението
  • Работа с операции за асинхронизация (например извличане на данни)
  • Разделяне на кода и състояние на приложение с мързеливо зареждане

Библиотеката на реактора

Първоначално написах Библиотеката на реакторите, за да сведем до минимум необходимия в моите лични проекти, които използват React. Една от неговите характеристики е супер простото управление на състоянието на приложението, което ще споделя с вас тук.

Оттогава реших да направя библиотеката достъпна за всички, които може би искат да опростят своя React / Redux код. Чувствайте се свободни да го използвате; твое е колкото моето.

Да инсталираш:

npm инсталирайте @ reactorlib / core

3 ключови неща

За да напишем нашето управление на състоянието на приложението с помощта на Reactor Library, има 3 ключови неща, които трябва да знаем за:

  • Магазин: Това е единственото място, където се съхранява цялото състояние на нашето приложение.
  • Същества: Това са части от състоянието на приложението, всяко от които представлява конкретна област на загриженост или функционалност.
  • Действия: Това са функции, които нашите компоненти могат да извикат, за да предизвикат някаква промяна в състоянието на приложението. Те също пребивават в магазина.

Стъпка 1: Създаване на единици

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

Reactor Library предоставя функция, наречена createEntity, която ще използваме за определяне на нашите субекти. Той приема два аргумента, реакциите на предприятието, както и първоначалното му състояние:

createEntity (реакции: обект, първоначално състояние: всякакви)

Първо да извадим по-лесната част НачалнатаState трябва основно да дефинира структурата на данните на нашето образувание, като му присвои стойност по подразбиране.

Аргументът на реакциите е картографиране на имената на действията спрямо съответните реакции. Обърнете внимание, че картографирането не е предназначено да дефинира действителните функции на действие.

В най-простата си форма реакцията изглежда така:

действие: (състояние, полезен товар) => newState

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

Ето един прост пример за дефиниция на образувание:

const InitiState = {стойност: 0};
const counter = createEntity (
  {
    прираст: (състояние, по) => (
      {... състояние, стойност: state.value + по}
    ),
    нулиране: състояние => ({... състояние, стойност: 0})
  }
  Първоначално състояние
);

ВАЖНО: При определяне на реакциите на субект, имайте предвид, че златното правило на React да не мутира състоянието на компонента се прилага и за състоянието на приложението. Така че, ако състоянието на вашето предприятие е от тип обект или масив, винаги не забравяйте да върнете нов обект или масив.

Лесен грах досега, нали? Нека да продължим ...

Стъпка 2: Настройка на магазина

Казах "магазина", защото може да има само един магазин в цялото ни приложение. За да направим този магазин достъпен за всички наши компоненти, ще трябва да го инжектираме в компонент от най-високо ниво, обикновено .

Reactor Library включва withStore HOC, който създава магазина, поставя субекти в него и определя целевия му компонент като доставчик / собственик на магазина.

withStore (субекти: Обект) (Компонент)

Тук аргументът на субектите е картографиране на имената на субекти спрямо действителните обекти на субекта, създадени с помощта на createEntity (). Това картографиране е важно, защото ние осъществяваме достъп до обекти от магазина, използвайки имената, посочени тук.

Нека вземем насрещното предприятие от предишния ни пример и да създадем нашия магазин, след което да поставим субекта в него:

брояч за импортиране от „./store/counter“;
const _App = () => (
  
    
  
);
const App = withStore ({counter}) (_ Приложение);

Толкова просто, наистина. Нашият магазин вече е готов.

Стъпка 3: Импортиране на подпори от магазина

Сега последната останала стъпка е да направим състоянието на приложението достъпно за нашите компоненти. Има 2 прости правила:

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

Използваме getPropsFromStore HOC на библиотеката на Reactor, за да направим едно или и двете, и да ги инжектираме в нашия компонент като реквизит.

getPropsFromStore (
  субекти ?: масив <низ>,
  действия ?: масив <стринги>
) (Компонент)

Тук субектите са списък с имена на образувания, а действията са списък с имена на действия.

Импортираните лица се инжектират като държавни реквизити. Това означава, че всеки път, когато някой от тези единици се промени, компонентът ще се представи отново.

Импортираните действия се инжектират като функционални подпори, които можем директно да извикаме вътре в нашия компонент.

Може би се чудите, къде да дефинираме тези функции на действие? Е, ние не Магазинът създава тези за нас въз основа на всички имена на действия, които сме картографирали към реакциите при създаването на нашите субекти с createEntity.

Продължавайки предишните ни примери, ние импортираме броячът от магазина, както следва:

const _ClickCount = ({брояч, увеличение, нулиране}) => (
  <>
    Кликнали сте {counter.value} пъти.
    <бутон onClick = {() => инкремент (1)}> Кликнете върху мен 
    <бутон onClick = {нулиране}> Нулиране на брояча 
  
);
const ClickCount = getPropsFromStore (
  [ "Брояч"],
  ['увеличение', 'нулиране']
) (_ ClickCount);

Това е! В 3 лесни стъпки сме свързали нашия компонент със състоянието на приложението.

Работа с действия на Async

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

С простата форма на реакция, изчисляването на ново състояние се извършва веднага. Но когато работи с асинхронни действия, субектът трябва да извърши асинхронна операция и да изчака да приключи, преди да може да изчисли промяната на състоянието. За това се нуждаем от различна форма на реакция, която умно се нарича реакция на асинхронизация.

Дефиниране на асинхронни реакции

CreateEntity на Reactor Library ни позволява лесно да дефинираме декларативно асинхронните реакции в следната форма:

действие: [
  (състояние, полезен товар) => newState,
  async (полезен товар, следващ) => {
    const резултат = изчакайте doSomethingAsync ();
    Следващата (резултат);
  }
  (състояние, резултат) => newState
]

Това е масив, състоящ се от 3 стъпки на нашата асинхронна реакция:

  1. Етапът на стартиране, при който може да се извърши всякаква подготвителна промяна в състоянието, напр. задаване на флаг „зареждане“ или „изчакване“.
  2. Стъпка асинхронизация, където образуванието извършва операцията за асинхронизация. Изчаква, докато операцията за асинхронизация приключи, преди да извикате следващата стъпка.
  3. Етапът на завършване, където се прави окончателната промяна на състоянието, обикновено се базира на резултата от предходния етап на асинхронизация.

Тази диаграма илюстрира как протичат данните през трите стъпки на асинхронната реакция:

Първата стъпка (стартиране) всъщност е незадължителна, тъй като има моменти, в които наистина нямате нужда от подготвителна промяна на състоянието.

Примерна употреба

Ето пример за цялостно образувание с прости и асинхронни реакции. Винаги можете да се върнете към илюстрацията по-горе, ако потокът от данни и промените в състоянието все още изглеждат малко неясни.

const InitiState = {auth: null, чака: false};
const сесия = createEntity (
  {
    Влизам: [
      състояние => ({... състояние, изчакване: вярно}),
      async ({потребителско име, парола}, следващ) => {
        const отговор = изчакайте влизане (потребителско име, парола);
        Следващата (отговор);
      }
      (състояние, {auth}) => ({... състояние, авт., чакане: невярно}),
    ],
    изход: state => ({... състояние, auth: null}),
  }
  Първоначално състояние
);

След като свикнете с този тристепенен формат, вие ще можете да създавате субекти бързо, защото ще трябва само да се съсредоточите върху логиката и потока на данните за промяна на състоянието, а не да се притеснявате за какъвто и да е сложен код на котлона, който да напишете.

Това е! Това не е ли твърде лесно?

Мързеливо зареждане на състоянието на приложението

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

Тъй като в приложението може да има само един магазин, библиотеката на Reactor предоставя прост начин за динамично обединяване на мързеливи магазини с функции в основния магазин. Това се използва withFeatureStore HOC, който има следния подпис:

withFeatureStore (субекти: Обект) (Компонент)

Както може да забележите, това има точно същия формат като withStore HOC, за който говорихме по-рано. Той посочва субекти, които са мързеливо заредени заедно с вашите модули с функции, за да уведомят Reactor Library да знае, че тези единици трябва да бъдат динамично обединени в магазина след зареждането на характеристичните модули.

Примерна употреба

Да вземем, например, мързеливо заредена функция на таймера, която има компонент на TimerPage като входна точка и таймерна единица, която да управлява състоянието си.

таймер за импортиране от './store/timer';
const _TimerPage = () => (
  <Обратно отброяване />
);
const TimerPage = withFeatureStore ({timer}) (_ TimerPage);

Това е! Отново бързо и лесно.

Допълнителна информация

За да научите повече за библиотеката на реакторите, която използвахме в тази статия, можете да намерите официалната й документация на https://github.com/arnelenero/reactorlib.

Благодаря за четенето.