Демистифициране на useReducer Hook на React: Разбиране на вътрешната му работа

В тази статия ще научим за куката useReducer и как тя може да ни помогне да разрешим класическия проблем на useState ада.

Представяме ви useReducer Hook

Какво е?

Просто още един вграден React Hook.

Каква е целта му?

За да стане ясно: Целта му е да имитира поведението на Redux. Осигурява механизъм за обработка на състоянието чрез Reduxi начин.

От техническа гледна точка: Куката useReducer в React осигурява структуриран начин за управление на състоянието чрез обработка на преходи на състояния чрез редуцираща функция, което прави сложната логика на състоянието и взаимозависимостите по-организирани и предвидими. Това е алтернатива на куката useState, подходяща за сценарии, при които управлението на състоянието става сложно.

Какъв е синтаксисът за използването му?

// Basic Example:

const [state, setState] = useReducer(reducerFunction, {name: ''});

function reducerFunction(state) {
  return {name: 'XYZ'};
}

function onInput() {
  setState();
} 


// Detailed Example: Extend to use Redux things.

const initialState = {name: '', age:23};

const [myState, setMyState] = useReducer(myReducerFunction, initialState);

function myReducerFunction(previousState, action) {
  // we have to return state from this function
   .
   .
   // your logic lies here which can modify the state

   const {type, payload} = action;
   switch (type) {
    case 'INCREMENT':
      return { ...previousState, count: state.count + payload.incrementBy};
    case 'DECREMENT':
      return { ...previousState,count: state.count - 1 };
    default:
      return previousState;
  }
}


function handleClick() {
  setMyState({
    type: 'Increment',
    payload: {incrementBy: 9}
  });
}

Нека разберем всеки ред:

Когато извикаме useReducer hook, тойприема 2 аргумента и връща масив с две стойности.

Първи аргумент е функция за обратно извикване, която се извиква, когато извикаме метода за настройка.

Вторият аргумент е състоянието initial.

В върнатия масив,

В първия индекс получаваме нашия updated state, а във втория индекс получаваме функция, която е сетерна функция, точно както получаваме такава в useState.

Неща, които трябва да се отбележат: Не е необходимо да предаваме обект като първоначално състояние, но като цяло, ако работим с обект useReducer, се препоръчва, защото най-вероятно ще имаме работа със сложно състояние

Какво е useState hell?

Нека първо го видим:

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

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

Сега това е просто, някои програмисти казват useState извиквания между функциите, те са по-сложни.

Терминът „useState hell“ се отнася до ситуация в разработката на React, при която прекомерното използване на куката useState за управление на състояние води до сложен, сложен и труден за поддръжка код.

Тази ситуация може да доведе до няколко проблема и предизвикателства:

Без предпазители:
При извикванията на useState, когато имаме много от тях, става трудно да актуализираме състоянието въз основа на определени условия и ако едно актуализиране на състояние зависи от стойността на друго състояние, тогава става по-трудно е да се проследят тези състояния.

Сложност на кода:
Когато управлението на състоянието на компонент разчита в голяма степен на useState, кодът може бързо да стане сложен. Множество useState извиквания и свързаната с тях логика могат да доведат до вложени структури и сложни актуализации.

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

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

Четимост: Прекомерните useState извиквания могат да затрупат кодовата база на вашия компонент, което прави предизвикателство да се идентифицира основната логика и прави компонентите по-малко четливи.

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

Влияние върху производителността: Всяко useState извикване задейства повторно изобразяване на компонента. Когато имате многобройни актуализации на състоянието, разпръснати из целия компонент, може да изпитате проблеми с производителността поради ненужни повторни изобразявания.

Трудност при преработване: С развитието на вашия компонент може да се наложи да преработите или разширите функционалността му. В „useState hell“ рефакторингът става по-рисков поради сложните взаимодействия между различните променливи на състоянието.

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

За такава ситуация използването на Reducer може да бъде спасител.

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

Ето един пример:

import React, { useReducer } from 'react';

const initialState = {
  count: 0,
  isLightOn: false,
  userName: '',
  todos: [],
  isLoading: false,
  showModal: false,
};

const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'TOGGLE_LIGHT':
      return { ...state, isLightOn: !state.isLightOn };
    case 'UPDATE_NAME':
      return { ...state, userName: action.payload };
    case 'ADD_TODO':
      return { ...state, todos: [...state.todos, action.payload] };
    case 'TOGGLE_LOADING':
      return { ...state, isLoading: !state.isLoading };
    case 'TOGGLE_MODAL':
      return { ...state, showModal: !state.showModal };
    default:
      return state;
  }
};

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>

      <div>
        Light: {state.isLightOn ? 'On' : 'Off'}
        <button onClick={() => dispatch({ type: 'TOGGLE_LIGHT' })}>Toggle Light</button>
      </div>

      <div>
        User: {state.userName}
        <input
          type="text"
          value={state.userName}
          onChange={(e) => dispatch({ type: 'UPDATE_NAME', payload: e.target.value })}
        />
      </div>

      <div>
        Todos: {state.todos.join(', ')}
        <button onClick={() => dispatch({ type: 'ADD_TODO', payload: 'New Todo' })}>Add Todo</button>
      </div>

      <div>
        Loading: {state.isLoading ? 'Yes' : 'No'}
        <button onClick={() => dispatch({ type: 'TOGGLE_LOADING' })}>Toggle Loading</button>
      </div>

      <div>
        Modal: {state.showModal ? 'Open' : 'Closed'}
        <button onClick={() => dispatch({ type: 'TOGGLE_MODAL' })}>Toggle Modal</button>
      </div>
    </div>
  );
}

export default App;

Други случаи на употреба, при които useReducer може да бъде от полза:

Предвидими актуализации на състоянието

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

Взаимозависими промени в състоянието

Ако една актуализация на състояние зависи от друга актуализация на състояние, можете да осигурите правилна последователност на преходите на състоянията в рамките на функцията за намаляване. Това може да предотврати условия на състезание или непоследователни актуализации, които могат да възникнат при отделни useState повиквания.

Глобално управление на държавата:

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

Оптимизация на производителността: (пакетни актуализации)

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

Тъй като useReducer пакетира актуализации на състоянието в рамките на едно изпращане, това може да помогне за предотвратяване на ненужни повторни изобразявания, които могат да възникнат при използване на множество useState извиквания. Тази оптимизация може да подобри производителността на вашия компонент.

Отстраняване на грешки и регистриране:

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

Трябва ли да спра да използвам Redux тогава?

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

Вместо това можем да използваме useReducer в определени раздели на нашето приложение, където нямаме нужда от redux. Например: състояния на странични чекмеджета, състояния на формуляри, състояния на диалогови прозорци и др.

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