Основи на разработката на Solid React: Най-добри практики за компоненти, състояние, кукички и изобразяване

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

Ще разгледаме примери относно компоненти, JSX, виртуален DOM, състояние и подпори и кукички.

Ето кратък преглед на примерите, през които ще преминем:

  1. Липса на повторна употреба: Архитектурата, базирана на компоненти, има за цел да насърчи повторната употреба. Някои разработчици обаче в крайна сметка създават компоненти, които са твърде специфични за конкретен случай на употреба. Стремете се да проектирате компоненти с обща функционалност, като ги направите по-адаптивни и многократно използвани в приложението.
  2. Смесване на потребителски интерфейс и бизнес логика: В идеалния случай компонентите трябва да се съсредоточат върху изобразяването на елементи на потребителския интерфейс, а не да обработват директно бизнес логиката или извличането на данни. Смесването на бизнес логиката с презентационния слой може да доведе до компоненти, които са тясно свързани и по-трудни за тестване.
  3. Без предоставяне на уникален ключ: Когато изобразявате списъци с елементи с помощта на JSX, важно е да предоставите уникален атрибут на ключ за всеки елемент от списъка. Пренебрегването на това може да доведе до проблеми с производителността и предупреждения в конзолата.
  4. Неправилно използване на JSX при условно изобразяване: Когато използват JSX в условни изрази, разработчиците може да забравят да включат резервна клауза по подразбиране или else, което може да доведе до неочаквано поведение или грешки.
  5. Директно манипулиране на DOM: Едно от основните предимства на използването на React е абстрахирането от директните манипулации на DOM. Някои разработчици, особено тези, които са нови за React, може да се опитат да манипулират директно DOM, заобикаляйки виртуалния DOM. Това може да доведе до неочаквано поведение и конфликти с вътрешния процес на съгласуване на React.
  6. Без използване на React фрагменти: Избягването на използването на React фрагменти (<>...</>) може да въведе ненужни родителски елементи във Virtual DOM дървото, което води до неоптимална производителност. Използвайте фрагменти, за да групирате елементи, без да добавяте допълнителни възли към DOM.
  7. Объркващо състояние и реквизити във функционални компоненти: Във функционалните компоненти състоянието и реквизитите са различни обекти. Някои разработчици може да ги объркат или да използват подпори, сякаш са в променливо състояние. Не забравяйте, че функционалните компоненти не трябва да променят своите подпори директно.
  8. Пренебрегване на почистването на състоянието: За компоненти, които извършват странични ефекти (напр. извиквания на API), непочистването на ресурсите при демонтиране на компонент може да доведе до изтичане на памет и проблеми с производителността.
  9. Не предоставя всички зависимости в useEffect: Когато използват куката useEffect, разработчиците трябва да посочат всички зависимости, от които зависи ефектът. Пропускането на зависимости може да доведе до неправилно поведение, тъй като ефектът може да не се повтори, когато се очаква.
  10. Без използване на useReducer за сложно състояние: В някои случаи разработчиците може да прекаляват с useState за управление на сложно състояние, когато useReducer може да предостави по-организирано решение.

Липса на повторна употреба

Нека демонстрираме концепцията за липса на повторна употреба с примери на код. Разгледайте следния пример от два компонента:

Пример 1: Специфични компоненти

// Component to display a user's profile with a specific layout
const UserProfileCard = ({ user }) => {
  return (
    <div className="user-profile-card">
      <img src={user.avatar} alt={user.name} />
      <h2>{user.name}</h2>
      <p>{user.bio}</p>
      {/* Specific actions related to user profile */}
      <button onClick={() => console.log("Follow")}>Follow</button>
      <button onClick={() => console.log("Message")}>Message</button>
    </div>
  );
};

// Component to display a product card with a specific layout
const ProductCard = ({ product }) => {
  return (
    <div className="product-card">
      <img src={product.image} alt={product.name} />
      <h2>{product.name}</h2>
      <p>{product.description}</p>
      {/* Specific actions related to the product */}
      <button onClick={() => console.log("Add to Cart")}>Add to Cart</button>
      <button onClick={() => console.log("View Details")}>View Details</button>
    </div>
  );
};

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

Пример 2: Компонент за многократна употреба

// A more generic component that can be used for different scenarios
const GenericCard = ({ title, description, image, actions }) => {
  return (
    <div className="generic-card">
      {image && <img src={image} alt={title} />}
      <h2>{title}</h2>
      <p>{description}</p>
      {/* Render actions dynamically */}
      <div className="card-actions">
        {actions.map((action, index) => (
          <button key={index} onClick={() => console.log(action)}>
            {action}
          </button>
        ))}
      </div>
    </div>
  );
};

Във втория пример имаме по-многократно използваем и общ компонент, наречен GenericCard. Той приема title, description, image и масив от actions като подпори, което му позволява да се използва за различни цели. С помощта на този компонент можем да създадем както картата на потребителския профил, така и картата на продукта с по-добра повторна употреба:

// Using the GenericCard component for the user profile card
const UserProfileCard = ({ user }) => {
  const actions = ["Follow", "Message"];
  return (
    <GenericCard
      title={user.name}
      description={user.bio}
      image={user.avatar}
      actions={actions}
    />
  );
};

// Using the GenericCard component for the product card
const ProductCard = ({ product }) => {
  const actions = ["Add to Cart", "View Details"];
  return (
    <GenericCard
      title={product.name}
      description={product.description}
      image={product.image}
      actions={actions}
    />
  );
};

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

Смесване на потребителски интерфейс и бизнес логика

Нека демонстрираме концепцията за смесване на потребителския интерфейс и бизнес логиката с примери на код, използвайки API на CoinGecko за извличане на данни за криптовалута. Ще започнем с пример, при който бизнес логиката е смесена с UI компонента, и след това ще го преработим, за да разделим правилно проблемите.

Ако искате да кодирате, трябва да инсталирате axios с npm i axios

Пример 1: Смесване на потребителски интерфейс и бизнес логика

import React, { useState, useEffect } from "react";
import axios from "axios";

const CryptoList = () => {
  const [cryptoData, setCryptoData] = useState([]);

  useEffect(() => {
    const fetchCryptoData = async () => {
      try {
        const response = await axios.get(
          "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=10&page=1&sparkline=false"
        );
        setCryptoData(response.data);
      } catch (error) {
        console.error("Error fetching crypto data:", error);
        setCryptoData([]);
      }
    };

    fetchCryptoData();
  }, []);

  return (
    <div>
      <h2>Top 10 Cryptocurrencies by Market Cap</h2>
      <ul>
        {cryptoData.map((crypto) => (
          <li key={crypto.id}>
            <strong>{crypto.name}</strong> ({crypto.symbol}): ${crypto.current_price}
          </li>
        ))}
      </ul>
    </div>
  );
};

В горния пример имаме компонент CryptoList, който извлича данни от API на CoinGecko, за да покаже 10-те най-добри криптовалути по пазарна капитализация. Компонентът обработва както изобразяването на потребителския интерфейс, така и логиката за извличане на данни, което го прави по-малко поддържаем и по-труден за тестване.

Пример 2: Разделяне на потребителския интерфейс и бизнес логиката

import React, { useState, useEffect } from "react";
import axios from "axios";

const fetchCryptoData = async () => {
  try {
    const response = await axios.get(
      "https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=10&page=1&sparkline=false"
    );
    return response.data;
  } catch (error) {
    console.error("Error fetching crypto data:", error);
    throw error;
  }
};

const CryptoList = ({ cryptoData }) => {
  return (
    <div>
      <h2>Top 10 Cryptocurrencies by Market Cap</h2>
      <ul>
        {cryptoData.map((crypto) => (
          <li key={crypto.id}>
            <strong>{crypto.name}</strong> ({crypto.symbol}): ${crypto.current_price}
          </li>
        ))}
      </ul>
    </div>
  );
};

const CryptoListContainer = () => {
  const [cryptoData, setCryptoData] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const data = await fetchCryptoData();
        setCryptoData(data);
      } catch (error) {
        setCryptoData([]);
      }
    };

    fetchData();
  }, []);

  return <CryptoList cryptoData={cryptoData} />;
};

В преработения пример сме разделили логиката за извличане на данни в нейната собствена функция fetchCryptoData. Компонентът CryptoList вече обработва само изобразяването на потребителския интерфейс, а компонентът CryptoListContainer обработва извличането на данни и предава данните като подпори на CryptoList.

Чрез правилното разделяне на проблемите кодът става по-поддържаем, по-лесен за тестване и следва по-добър архитектурен модел. Бизнес логиката е отделена от потребителския интерфейс, което позволява по-добра организация на кода и повторно използване. Освен това преработеният подход позволява по-лесно тестване на логиката за извличане на данни независимо от потребителския интерфейс, тъй като вече може да се тества с фалшиви данни или тестови извиквания на API. Това разделяне на проблемите води до по-модулна и мащабируема кодова база.

Не предоставя уникален ключ

Нека демонстрираме важността на предоставянето на уникален атрибут key при рендиране на списъци с елементи в React с примери на код.

Пример 1: Непредоставяне на уникален ключ (неправилен)

import React from "react";

const TodoList = ({ todos }) => {
  return (
    <ul>
      {todos.map((todo) => (
        <li>{todo.title}</li>
      ))}
    </ul>
  );
};

export default TodoList;

В горния пример имаме TodoList компонент, който получава масив от todos като реквизити и изобразява списък със задачи, без да предоставя уникален атрибут key.

Пример 2: Предоставяне на уникален ключ (правилен)

import React from "react";

const TodoList = ({ todos }) => {
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
};

export default TodoList;

В коригирания пример добавихме уникален атрибут key към всеки елемент <li>, като използвахме todo.id като ключ. Атрибутът key помага на React ефективно да актуализира списъка, когато настъпят промени, тъй като служи като уникален идентификатор за всеки елемент в списъка.

Важност на предоставянето на уникален ключ: Когато React изобразява списък от елементи, той се нуждае от начин да следи всеки елемент и да го актуализира ефективно, когато списъкът се промени. Атрибутът key предоставя този механизъм за React. Без уникален key за всеки елемент от списъка, може да се наложи React да изобрази отново целия списък, когато настъпят промени, дори за незначителни актуализации, което може да доведе до проблеми с производителността, особено за големи списъци.

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

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

Неправилно използване на JSX при условно изобразяване

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

Пример 1: Използване на JSX неправилно (неправилно)

import React from "react";

const Greeting = ({ isLoggedIn }) => {
  if (isLoggedIn) {
    return <h1>Welcome, User!</h1>;
  }
  // Missing the default fallback for when isLoggedIn is false
}

В горния пример компонентът Greeting използва условен израз, за ​​да изобрази приветстващо съобщение, ако потребителят е влязъл. Компонентът обаче забравя да включи резервен вариант по подразбиране, когато isLoggedIn е невярно.

Пример 2: Използване на JSX правилно (правилно)

import React from "react";

const Greeting = ({ isLoggedIn }) => {
  return (
    <>
      {isLoggedIn ? (
        <h1>Welcome, User!</h1>
      ) : (
        <h1>Welcome, Guest!</h1>
      )}
    </>
  );
};

В коригирания пример сме използвали троичния оператор, за да предоставим резервен вариант по подразбиране (<h1>Welcome, Guest!</h1>), когато isLoggedIn е невярно. Чрез използването на троичен оператор или други техники за условно изобразяване ние гарантираме, че компонентът винаги връща валидно JSX съдържание, независимо от условието.

Значение на правилното използване на JSX при условно изобразяване: Правилното използване на JSX при условно изобразяване е от съществено значение за избягване на неочаквано поведение или грешки. Когато изобразявате JSX в рамките на условни изрази, е изключително важно да включите резервен вариант по подразбиране или клауза „друго“, за да се справите със сценарии, при които условието не е изпълнено.

Пренебрегването на включването на резервен вариант по подразбиране може да доведе до:

  • Грешки при изобразяване: Компонентът може да не успее да изобрази или да изведе грешка, когато условието не е изпълнено, тъй като React очаква JSX съдържание за всички сценарии.
  • Празно съдържание: Без резервен вариант по подразбиране, компонентът може да рендира празно съдържание или изобщо нищо, когато условието не е изпълнено, което води до непълен потребителски интерфейс.

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

Директно манипулиране на DOM

Нека демонстрираме проблема с директното манипулиране на DOM в React с примери на код.

Пример 1: Директно манипулиране на DOM (неправилно)

import React, { useEffect } from "react";

const DirectDOMManipulation = () => {
  useEffect(() => {
    // Directly manipulating the DOM (bad practice in React)
    const titleElement = document.getElementById("title");
    if (titleElement) {
      titleElement.style.color = "red";
    }
  }, []);

  return <h1 id="title">Hello, World!</h1>;
};

В горния пример имаме компонент с име DirectDOMManipulation. В куката useEffect ние директно манипулираме DOM, като променяме цвета на заглавния елемент с ID 'title' на червен.

Пример 2: Използване на React State за манипулиране на DOM (правилно)

import React, { useState } from "react";

const StateBasedDOMManipulation = () => {
  const [titleColor, setTitleColor] = useState("black");

  const handleColorChange = () => {
    setTitleColor("red");
  };

  return (
    <div>
      <h1 style={{ color: titleColor }}>Hello, World!</h1>
      <button onClick={handleColorChange}>Change Color</button>
    </div>
  );
};

В коригирания пример сме използвали React състояние, за да управляваме цвета на заглавния елемент. Цветът се съхранява в променливата на състоянието titleColor и ние използваме функцията handleColorChange, за да актуализираме състоянието и, следователно, цвета на заглавието.

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

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

Значение на избягването на директна манипулация на DOM: Избягването на директна манипулация на DOM в React е от съществено значение поради следните причини:

  1. Поддръжка: Използването на управлението на състоянието и компонентите на React прави кода по-поддържаем и по-лесен за разсъждение, тъй като работите в контролираната среда на React.
  2. Предвидими актуализации: Виртуалният DOM на React позволява ефективни актуализации и гарантира, че действителните промени на DOM са сведени до минимум, което води до по-добра производителност.
  3. Повторна употреба: Директното манипулиране на DOM може да доведе до неизползваем код, тъй като логиката е тясно свързана със специфични елементи и структура.
  4. Избягване на конфликти: Директното манипулиране на DOM може да влезе в конфликт с вътрешните процеси на React, което води до неочаквано поведение и грешки.

Като позволите на React да обработва актуализациите на DOM чрез своя Virtual DOM, можете да се възползвате от оптимизациите на производителността на рамката и да осигурите по-гладък, по-предвидим потребителски интерфейс.

Без използване на React фрагменти

Нека демонстрираме важността на използването на React Fragments с примери на код.

Пример 1: Неизползване на React фрагменти (неправилно)

import React from "react";

const ListWithoutFragments = () => {
  return (
    <div>
      <h2>Items</h2>
      <ul>
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
      </ul>
    </div>
  );
};

В горния пример имаме ListWithoutFragments компонент, който изобразява списък от елементи в рамките на родител <div>. Без да използваме React Fragments, ние въвеждаме ненужен родителски елемент (<div>), за да групираме заглавието и списъка.

Пример 2: Използване на React фрагменти (правилно)

import React from "react";

const ListWithFragments = () => {
  return (
    <>
      <h2>Items</h2>
      <ul>
        <li>Item 1</li>
        <li>Item 2</li>
        <li>Item 3</li>
      </ul>
    </>
  );
};

В коригирания пример сме използвали React Fragments (<>...</>), за да групираме заглавието и списъка, без да добавяме допълнителен родителски елемент към Virtual DOM дървото.

Обяснение: React Fragments ви позволяват да групирате множество елементи заедно, без да въвеждате допълнителен родителски елемент във Virtual DOM дървото. Използването на фрагменти е особено полезно, когато не искате да добавяте допълнително маркиране към изобразения HTML, но все пак трябва да групирате елементи заедно логически.

В първия пример, без да използваме React Fragments, увихме заглавието и списъка в <div>. Въпреки че това е валиден JSX синтаксис, той въвежда ненужен <div> елемент в DOM дървото. В по-сложни компоненти това може да доведе до по-дълбоко и по-голямо DOM дърво, което може да повлияе на производителността, особено по време на съгласуване и актуализации на изобразяване.

Във втория пример, като използваме React Fragments (<>...</>), ние групираме елементите, без да добавяме допълнителни възли към DOM. Виртуалното DOM дърво остава по-леко и ефективно, тъй като не включва допълнителния родителски елемент (<div>) в структурата.

Значение на използването на React фрагменти: Използването на React фрагменти е от съществено значение поради следните причини:

  1. Ефективност: Фрагментите спомагат за поддържането на DOM дървото плитко, като намаляват общото използване на паметта и подобряват производителността на изобразяване, особено за големи и сложни компоненти.
  2. По-чист JSX: Фрагментите позволяват по-чист JSX код, като елиминират необходимостта от ненужни родителски елементи за групиране на елементи заедно.
  3. Избягване на стилови конфликти: Избягването на допълнителни родителски елементи може да помогне за предотвратяване на непреднамерени стилови конфликти и да поддържа по-чисто разделяне на проблемите в CSS.
  4. По-добра четливост: React Fragments правят JSX кода по-четлив и изразителен чрез изрично посочване на логическото групиране на елементи.

Чрез включването на React фрагменти във вашите компоненти можете да създавате по-ефективни и поддържаеми React приложения, като същевременно поддържате DOM дървото възможно най-просто.

Объркващо състояние и реквизити във функционални компоненти

Нека демонстрираме проблема с объркващо състояние и подпори във функционални компоненти с примери на код.

Пример 1: Объркващо състояние и реквизити (неправилно)

import React, { useState } from "react";

const ConfusingStateAndProps = ({ initialCount }) => {
  // Incorrect usage: Using props as if it were mutable state
  const [count, setCount] = useState(initialCount);

  const handleIncrement = () => {
    // Directly modifying the initialCount prop (incorrect)
    initialCount += 1;
    setCount(initialCount); // This will not cause a re-render!
  };

  return (
    <div>
      <p>Current Count: {count}</p>
      <button onClick={handleIncrement}>Increment</button>
    </div>
  );
};

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

Пример 2: Използване на подпори и състояние правилно (правилно)

import React, { useState } from "react";

const CorrectUsageOfPropsAndState = ({ initialCount }) => {
  // Correct usage: Use the initialCount prop directly and use useState for state
  const [count, setCount] = useState(initialCount);

  const handleIncrement = () => {
    // Correct approach: Use setCount to update the state
    setCount(count + 1);
  };

  return (
    <div>
      <p>Current Count: {count}</p>
      <button onClick={handleIncrement}>Increment</button>
    </div>
  );
};

В коригирания пример ние използваме initialCount prop директно, за да инициализираме състоянието count с помощта на useState. След това актуализираме правилно състоянието, като използваме функцията setCount вътре във функцията handleIncrement. Ние не променяме initialCount проп директно.

Обяснение: Във функционалните компоненти подпорите и състоянието са различни обекти с различни цели:

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

От съществено значение е да се прави разлика между подпори и състояние във функционалните компоненти и да се използват по подходящ начин:

  • Реквизити: Използвайте реквизити за предаване на данни от родителски компоненти към дъщерни компоненти. Те трябва да се третират като само за четене и да не се променят директно от приемащия компонент.
  • Състояние: Използвайте състояние за управление на променливи данни в компонент. Актуализирайте състоянието, като използвате функциите за настройка, предоставени от куките за управление на състоянието на React (useState, useReducer и т.н.).

Важност на това да не се объркват състоянието и реквизитите: Объркването на състоянието и реквизитите може да доведе до неочаквано поведение и грешки във вашите React приложения. От решаващо значение е да запомните съответните им роли и да следвате най-добрите практики:

  1. Предсказуемо поведение: Правилното използване на реквизити и състояние гарантира очакваното поведение на вашите компоненти и спомага за поддържане на предвидимостта на състоянието на вашето приложение.
  2. Поддържаемост на кода: Правилното разграничаване между състояние и реквизити прави вашата кодова база по-поддържаема и по-лесна за разсъждение за вас и другите разработчици.
  3. Повторна употреба: Като не модифицирате реквизити директно, вие гарантирате, че вашите компоненти остават по-многократно използвани и могат да се използват в различни контексти без неочаквани странични ефекти.

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

Пренебрегване на State Cleanup

Нека демонстрираме проблема с игнорирането на почистването на състоянието във функционални компоненти с примери на код.

Пример 1: Пренебрегване на почистването на състоянието (неправилно)

import React, { useState, useEffect } from "react";
import axios from "axios";

const DataFetchingComponent = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      const response = await axios.get("https://api.example.com/data");
      setData(response.data);
    };

    fetchData();
  }, []);

  // Missing cleanup function to cancel the API request

  return (
    <div>
      <h2>Data List</h2>
      <ul>
        {data.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
};

В горния пример имаме DataFetchingComponent, който извлича данни от API с помощта на куката useEffect. Забравихме обаче да включим функцията за почистване в useEffect за анулиране на заявката за API, когато компонентът се демонтира.

Пример 2: Добавяне на почистване на състояние (правилно)

import React, { useState, useEffect } from "react";
import axios from "axios";

const DataFetchingComponent = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    let isMounted = true;

    const fetchData = async () => {
      try {
        const response = await axios.get("https://api.example.com/data");
        if (isMounted) {
          setData(response.data);
        }
      } catch (error) {
        // Handle error
      }
    };

    fetchData();

    return () => {
      // Cleanup function to cancel the API request
      isMounted = false;
    };
  }, []);

  return (
    <div>
      <h2>Data List</h2>
      <ul>
        {data.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
};

В коригирания пример добавихме функция за почистване вътре в куката useEffect чрез връщане на функция. Тази функция за почистване задава флаг (isMounted) на false, когато компонентът се демонтира, което помага за предотвратяване на актуализацията на състоянието, ако компонентът е демонтиран, преди да бъде получен отговорът на API.

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

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

  1. Изтичане на памет: Ако даден компонент бъде демонтиран преди страничният ефект (напр. заявка за API) да завърши, компонентът може да се опита да актуализира състоянието си, след като бъде демонтиран. Това може да доведе до изтичане на памет и потенциални грешки.
  2. Ненужни актуализации: Пренебрегването на почистването може да доведе до актуализации на състоянието на компонента дори след като той вече не е в DOM, причинявайки ненужно изобразяване и разточително използване на ресурси.

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

Значение на почистването на състоянието във функционалните компоненти: Правилното почистване на състоянието е от решаващо значение поради следните причини:

  1. Управление на паметта: Почистването гарантира, че в паметта не остават препратки към немонтирани компоненти, предотвратявайки изтичане на памет в дълго работещи приложения.
  2. Съгласуваност на данните: Почистването не позволява на компонентите да се опитват да актуализират състоянието си или да взаимодействат с ресурсите, след като бъдат демонтирани, като избягва непоследователно поведение на приложението.
  3. Оптимална производителност: Чрез почистване на ресурсите вие ​​гарантирате, че приложението работи ефективно, като избягвате ненужната обработка и рендиране за немонтирани компоненти.

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

Не предоставя всички зависимости в useEffect

Нека демонстрираме проблема с непредоставянето на всички зависимости в куката useEffect с примери на код.

Пример 1: Не се предоставят всички зависимости (неправилно)

import React, { useState, useEffect } from "react";

const CounterComponent = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      // Incorrect usage: Not providing count as a dependency
      setCount(count + 1);
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  return (
    <div>
      <p>Current Count: {count}</p>
    </div>
  );
};

В горния пример имаме CounterComponent, който увеличава състоянието count всяка секунда, използвайки функцията setInterval в рамките на куката useEffect. Въпреки това сме забравили да предоставим count като зависимост към куката useEffect.

Пример 2: Предоставяне на всички зависимости (правилно)

import React, { useState, useEffect } from "react";

const CounterComponent = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      // Correct approach: Include count as a dependency
      setCount((prevCount) => prevCount + 1);
    }, 1000);

    return () => clearInterval(interval);
  }, [count]);

  return (
    <div>
      <p>Current Count: {count}</p>
    </div>
  );
};

В коригирания пример сме включили състоянието count като зависимост в куката useEffect, като го добавихме към масива на зависимости [count]. Това гарантира, че ефектът се изпълнява отново, когато състоянието count се промени, което е планираното поведение.

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

Пропускането на зависимости може да доведе до неправилно поведение поради следните причини:

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

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

Важност на предоставянето на всички зависимости в useEffect: Предоставянето на всички зависимости в куката useEffect е от решаващо значение поради следните причини:

  1. Правилно поведение: Посочването на всички зависимости гарантира, че ефектът се изпълнява, когато съответните зависимости се променят, което води до правилно и последователно поведение.
  2. Предотвратяване на странични ефекти: Включването на всички зависимости помага за предотвратяване на нежелани странични ефекти или остарели данни в компонента.
  3. Ефективност: Точното определяне на зависимости помага за оптимизиране на изобразяването и предотвратява ненужните повторни изобразявания.

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

Без използване на useReducer за сложно състояние

Нека демонстрираме проблема с неизползването на useReducer за сложно управление на състоянието с примери на код.

Пример 1: Без използване на useReducer

import React, { useState } from "react";

const ComplexStateComponent = () => {
  const [counter, setCounter] = useState(0);
  const [userName, setUserName] = useState("");
  const [isAdmin, setIsAdmin] = useState(false);
  // ... additional state variables and their setters

  // Complex logic to update state with multiple setters scattered throughout the component
  const handleIncrement = () => {
    setCounter(counter + 1);
  };

  const handleNameChange = (event) => {
    setUserName(event.target.value);
  };

  const handleAdminToggle = () => {
    setIsAdmin(!isAdmin);
  };

  // ... more complex logic and state updates

  return (
    <div>
      <p>Counter: {counter}</p>
      <input type="text" value={userName} onChange={handleNameChange} />
      <button onClick={handleIncrement}>Increment</button>
      <button onClick={handleAdminToggle}>
        {isAdmin ? "Revoke Admin" : "Grant Admin"}
      </button>
      {/* Additional JSX and components rendering */}
    </div>
  );
};

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

Пример 2: Използване на useReducer

import React, { useReducer } from "react";

const initialState = {
  counter: 0,
  userName: "",
  isAdmin: false,
  // ... additional state properties
};

const stateReducer = (state, action) => {
  switch (action.type) {
    case "INCREMENT":
      return { ...state, counter: state.counter + 1 };
    case "UPDATE_USERNAME":
      return { ...state, userName: action.payload };
    case "TOGGLE_ADMIN":
      return { ...state, isAdmin: !state.isAdmin };
    // ... additional action types and state updates
    default:
      return state;
  }
};

const ComplexStateComponent = () => {
  const [state, dispatch] = useReducer(stateReducer, initialState);

  // Complex logic handled by the reducer with organized actions
  const handleIncrement = () => {
    dispatch({ type: "INCREMENT" });
  };

  const handleNameChange = (event) => {
    dispatch({ type: "UPDATE_USERNAME", payload: event.target.value });
  };

  const handleAdminToggle = () => {
    dispatch({ type: "TOGGLE_ADMIN" });
  };

  // ... more complex logic and state updates handled by dispatch

  return (
    <div>
      <p>Counter: {state.counter}</p>
      <input type="text" value={state.userName} onChange={handleNameChange} />
      <button onClick={handleIncrement}>Increment</button>
      <button onClick={handleAdminToggle}>
        {state.isAdmin ? "Revoke Admin" : "Grant Admin"}
      </button>
      {/* Additional JSX and components rendering */}
    </div>
  );
};

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

Обяснение: useState е мощна кука за управление на просто състояние в React компоненти. Въпреки това, тъй като сложността на управлението на състоянието се увеличава, използването на множество useState куки може да доведе до код, който става по-труден за поддържане, разбиране и разширяване. В резултат на това useReducer става по-подходяща алтернатива за сложно управление на състоянието.

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

Значение на използването на useReducer за комплексно състояние: Използването на useReducer за сложно управление на състоянието предлага няколко предимства:

  1. Организирана логика на състоянието: Функцията за намаляване централизира актуализациите на състоянието, което прави логиката по-лесна за следване и поддържане.
  2. Модулност: Действията могат да се дефинират и изпращат от различни части на компонента или дори от други компоненти, насърчавайки модулен дизайн.
  3. Предсказуемо поведение: Редукционният модел насърчава предвидимостта и последователността в актуализациите на състоянието чрез придържане към еднопосочния поток от данни.
  4. Поддържаемост на кода: Тъй като компонентът расте и управлението на състоянието става по-сложно, useReducer улеснява разширяването и управлението на сложността.

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

Заключение: В тази статия проучихме десет често срещани грешки, които разработчиците могат да срещнат, докато работят с React. Разбирането и избягването на тези капани може значително да подобри процеса на разработка и цялостното качество на React приложенията.

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