Список покупок с помощью useReducer React Hook — небольшое упражнение, чтобы упростить понимание useReducer.

С появлением Redux как решения для управления состоянием для React концепция Reducer стала популярной в JavaScript. Мое понимание редюсеров всегда было неоднозначным, так как useReducer предоставляется React как продвинутый хук, я решил немного поупражняться с ним.

Большинство примеров редукторов, которые я видел, относятся к некоторым функциям счета, а также к увеличению или уменьшению числа и т. д. В этой статье я хочу шаг за шагом создать простой список покупок, чтобы лучше понять этот хук, вместо useState, я хочу попробовать useReducer.

*useReducer отличается от useEffect, useState, useRef и других перехватчиков. Без него мы можем нормально завершить разработку требований, но useReducer может сделать наш код более читабельным, удобным в сопровождении и предсказуемым.

По сути, редукторы используются для управления состоянием в приложении. Например, если пользователь вводит что-то в поле ввода HTML, приложение должно управлять этим состоянием пользовательского интерфейса (например, контролируемым компонентом).

По сути, редюсер — это функция, которая принимает два параметра — текущее состояние и действие — и возвращает новое состояние по этим двум параметрам. Это может быть выражено как:

(state, action) => newState

Первый параметр: state 📍

Функция-редуктор — это чистая функция без каких-либо побочных эффектов. Это означает, что при одних и тех же входных данных (например, состоянии и действии) ожидаемый результат (например, newState) всегда будет одним и тем же.

*Объект state, обрабатываемый редуктором, должен быть неизменяемым, что означает, что никогда нельзя напрямую изменять состояние в параметре, функция редуктора должна каждый раз возвращать новое состояние. .

Второй параметр: действие 📍

Действие обычно определяется как объект с атрибутом type, в зависимости от типа действия редьюсеры могут выполнять условные переходы между состояниями. Если тип действия не соответствует ни одному из условий, он вернет исходное состояние.

🤯 Текстовые объяснения могут быть головной болью, так почему бы просто не начать делать наш список покупок…

Наш список покупок довольно прост, пользователь может ввести текст в поле ввода, нажав кнопку добавить, каждый список будет отображаться ниже с кнопкой удалить, которая позволяет пользователю легко удалить этот список. У нас также есть кнопка reset, которая очищает списки.

Чтобы сделать структуру понятной, мы можем условно разделить страницу на три компонента: AddWrapper содержит кнопку добавления и поле ввода, ListWrapper показывает окончательный список покупок и кнопки удаления, а кнопка сброса находится внутри ResetButtonWrapper.

Файл reducer.js будет содержать список покупок Reducer и исходный список покупок в качестве начального состояния, примерно так:

const initialShoppingList = [ { id:'',content:'' } ]
const shoppinglistReducer = ( state, action ) => {...}
export { shoppinglistReducer, initialShoppingList }

Компонент Приложение:

export default function App() {
  const inputRef = useRef();
  ...
  return (
     <>
       ...
       <AddWrapper ... />
       <ListWrapper ... />
       <ResetButtonWrapper ... />
     </>
 )}

Компонент AddWrapper:

*если вы не знакомы с forwardRef, вы можете взглянуть на эту статью :)

import { forwardRef } from "react";
const AddWrapper = ({ ... }, ref) => (
  <>
    <input type="text" ref={ref} />
    <button onClick={...}>add</button>
  </>
);
export default forwardRef(AddWrapper);

Компонент ListWrapper:

const ListWrapper = ({ ... }) => {
 return (
   <ul>
    {newList?.filter(...).map(({ ... }) => {
       return (
         <li key={...}>
           <span>{content}</span>
           <button onClick={...}>remove</button>
        </li>
     )})}
   </ul>
)};
export default ListWrapper;

Компонент ResetButtonWrapper:

const ResetButtonWrapper = ({ ... }) => 
  <button onClick={...}>reset</button>
export default ResetButtonWrapper;

Структура ясна, теперь давайте начнем с логики добавить.

Чтобы добавить список, нам нужен начальный массив списка, который содержит объект списка, например текстовое содержимое, идентификатор и т. д. Мы уже создали его в файле reducer.js выше.

const initialShoppingList = [ { id:'',content:'' } ];

Наша логика «добавления» также находится внутри файла редуктора, назовем его shoppinglistReducer. Поскольку редьюсер получает текущее состояние и инициированное действие (например, добавить, удалить…), он вычисляет и возвращает новое состояние (например, новый список покупок), мы можем понять u*s*e*R *e*d*u*c*e*r вот так:

🦋 Используйте действие типа по отправке, чтобы вызвать действие и обновить начальный в новое состояние.

Поскольку наш исходный список содержит id и content, это означает, что мы можем реализовать логику добавления:

const shoppinglistReducer = (state, action) => {
  switch ( action.type ) {
   case "ADD":
    return [...state, {id: state.length, content: action.content}];
   case "REMOVE": return...;
   case "RESET": return...;
   default: throw new Error();
 }}

С типом действия «ADD» мы добавляем новый список с идентификатором и вводимый текст в исходный список.

Теперь мы можем использовать эту логику внутри компонента App и передать вниз props:

import { shoppinglistReducer, initialShoppingList } from "./reducer";
import { useReducer, useRef } from "react";
export default function App() {
   const inputRef = useRef(); // 👉 create input ref
   const [newList, dispatch] = useReducer(shoppinglistReducer, initialShoppingList); // 👉 dispatch action from the reducer, update initial list and return new list
   const addList = () => {
      inputRef.current.value && dispatch({ type: "ADD", content: inputRef.current.value });
      inputRef.current.value = ""; // reset input value after click
      inputRef.current.focus();
   };
 return (
   <AddWrapper addList={addList} ref={inputRef} />
   <ListWrapper newList={newList} />
   ...
 )
}

Обновите компонент AddWrapper:

Теперь пользователь может просто ввести и добавить список, новый массив списка покупок возвращается из редюсера, чтобы отобразить его, мы просто делаем .map():

const ListWrapper = ({ newList, ... }) => {
  return (
    <ul>
    {newList?.map(({ id, content }) => {
      return (
        <li key={id}>
         <span>{content}</span>
         <button onClick={...}>remove</button> 
        </li>
      );
    })}
   </ul>
 )};
export default ListWrapper;

Следующий шаг — создание логики remove.

Поскольку мы уже создали редьюсер, мы можем просто добавить эту логику:

const shoppinglistReducer = (state, action) => {
  switch ( action.type ) {
   case "ADD":... //same
   case "REMOVE": return state.filter(list => list.id !== action.id);
   case "RESET": return...;
   default: throw new Error();
 }}
export default function App() {
  const remove = (id) => dispatch({ type: "REMOVE", id });
   return (
     ...
     <ListWrapper newList={newList} remove={remove} />
 )}
const ListWrapper = ({ newList, remove }) => {
  return (
    <ul>
      ... // same 
       <button onClick={() => remove(id)}>remove</button>
    </ul>
 )};

Приведенный выше код означает: если мы хотим «УДАЛИТЬ» определенный список, мы используем ID и отфильтровываем соответствующий список, а остальные отображаем как « новый список». Чтобы вызвать эту функцию удаления при нажатии кнопки, мы используем идентификатор newList.

Теперь пользователь может просто удалить список нажатием кнопки.

Последний шаг: создайте логику reset.

Это довольно просто, «сброс» просто означает, что ничего не рендерится, поэтому мы можем просто вернуть пустой массив [ ]:

const shoppinglistReducer = (state, action) => {
  switch ( action.type ) {
   case "ADD": ...; 
   case "REMOVE": ...;
   case "RESET": return[];
   ...
 }}
export default function App() {
    const handleReset = () => dispatch({ type: "RESET" });
    return (
     ...
     <ResetButtonWrapper handleReset={handleReset} />
}
const ResetButtonWrapper = ({ handleReset }) => 
   <button onClick={() => handleReset()}>reset</button>;

Заключение

useReducer — это альтернатива useState, обычно предпочтительнее использовать useState, когда у вас сложная логика состояния, включающая несколько подзначений, или когда следующее состояние зависит от предыдущего, это также позволяет оптимизировать производительность для компонентов, запускающих глубокие обновления. потому что вы можете передать dispatch вместо обратных вызовов.

Используйте его, если:

  • ваше состояние представляет собой массив или объект
  • изменения вашего состояния сложны
  • вы хотите создавать автоматизированные тест-кейсы для обеспечения стабильности программы
  • вам нужно изменить некоторое состояние в глубоких подкомпонентах
  • у вас относительно большое приложение, вы надеетесь, что пользовательский интерфейс и бизнес можно поддерживать отдельно

Вот и все! 👐 Вы можете найти полный код здесь, поиграть с ним :) Спасибо за ваше время! ⏰

Если вас также интересуют другие мои статьи, вот несколько ссылок:











Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord . Заинтересованы в хакинге роста? Ознакомьтесь с разделом Схема.