15 - Про useEffect, часть 2, массив зависимостей

Оценить качество материала и подачу материала автором видео:

Front-end

Трудоустройтесь middle front-end разработчиком на React JS (TypeScript) за 12-16 месяцев обучения с ежедневной менторской поддержкой в формате видео 1 на 1 и коммерческими проектами в портфолио

Перейти на курс
Front-end

Back-end

Трудоустройтесь middle back-end разработчиком за 12-16 месяцев обучения с ежедневной менторской поддержкой в формате видео 1 на 1 и коммерческими проектами в портфолио

Перейти на курс
Back-end

Карьерный бустер

Получите коммерческий опыт на реальных стартапах, прокачайте tech & soft навыки, научитесь работать в команде, проходить собеседования и получите первую работу в IT!

Перейти на курс
Карьерный бустер

Основы Front-end

Сделайте первый шаг в IT, освоив базовые знания разработки и научившись создавать небольшие проекты на JavaScript

Перейти на курс
Основы Front-end

Основы Back-end

Сделайте первый шаг в IT, освоив базовые знания разработки. Без опыта. Без математики. Только практика: JavaScript, SQL, Node JS, база данных

Перейти на курс
Основы Back-end

Основы хука useEffect в React

Автор конспекта: Стогниева Виктория

Введение: Роль React и "Внешний Мир"🌍

📌

Основная задача React — эффективное управление DOM-деревом (тем, что пользователь видит в браузере) на основе данных, которые мы называем состоянием (state).

React контролирует цикл "данные → разметка", но реальные приложения взаимодействуют с внешним миром, который включает:

  • API браузера (document.title, localStorage)
  • Сетевые запросы к серверам
  • Таймеры (setTimeout, setInterval)
  • Подписки на события
📌

UseEffect — это инструмент для безопасного взаимодействия с внешним миром и синхронизации его с состоянием приложения.

1.Что такое "Побочные Эффекты" (Side Effects)? ⚡

Побочный эффект — это любое взаимодействие с внешним миром, которое:

  • ❌ Не является частью основного рендеринга
  • 🌍 Выходит за пределы React-компонента

Классический пример — изменение заголовка вкладки браузера (document.title). Это побочный эффект, потому что тег title находится в head части HTML-документа, а React управляет только содержимым одного корневого элемента (обычно <div id="root">).

📌

UseEffect — единственный правильный способ управления побочными эффектами в функциональных компонентах.

2. Как работает useEffect: Ключевые концепции 🔧

Основная идея

UseEffect синхронизирует программу с внешним миром. React решает, когда выполнить эффект.

Различие понятий

  • Хук useEffect — вызывается при каждом рендере
  • Функция-эффектcallback, который создается при каждом рендере, но запускается только при изменении зависимостей

⏱ Время выполнения

Эффект выполняется после обновления DOM:

  1. React вызывает ваш компонент.➡️
  2. Компонент возвращает новый JSX.➡️
  3. React сравнивает новый JSX со старым, обновляет DOM.➡️
  4. Пользователь видит обновленный интерфейс.➡️
  5. React запускает вашу функцию-эффект.➡️ Это гарантирует, что код эффекта не будет блокировать отрисовку интерфейса.

3. Управление запуском эффекта: массив зависимостей 🎛

📌

Второй аргумент useEffectмассив зависимостей — это инструкция для React. С её помощью мы сообщаем, при изменении каких данных нужно перезапускать наш эффект.

3.1. 🔄 Эффект после каждого рендера (без массива)

jsx
useEffect(() => {
  // Код внутри эффекта выполняется после каждого завершенного рендера компонента
})

3.2. ✅ Эффект только при монтировании (пустой массив)

jsx
useEffect(() => {
  // Код выполняется только один раз - после первого рендера (монтирования компонента)
}, [])

Важно: мыслите зависимостями, а не жизненным циклом!

4. Практический пример: Счетчик в заголовке страницы 🧪

Пример с одним счетчиком

jsx
import { useState, useEffect } from "react"
 
export function App() {
  console.log("App")
  const [counter, setCounter] = useState(1)
 
  useEffect(() => {
    document.title = `Счетчик: ${counter}`
  })
 
  return (
    <div>
      <button onClick={() => setCounter(counter + 1)}>Increment: {counter}</button>
    </div>
  )
}

Пример с двумя счетчиками

jsx
export function App() {
  console.log("App")
  const [counter, setCounter] = useState(1)
  const [counter2, setCounter2] = useState(1)
 
  useEffect(() => {
    document.title = `Счетчик: ${counter}`
  }, [counter]) // ✅ Теперь зависит только от counter
 
  return (
    <div>
      <button onClick={() => setCounter(counter + 1)}>Increment: {counter}</button>
      <button onClick={() => setCounter2(counter2 + 1)}>Increment 2: {counter2}</button>
    </div>
  )
}

Вопрос: что произойдет с заголовком, если нажать на вторую кнопку (Increment 2)?

Ответ: заголовок НЕ изменится. Поскольку useEffect теперь зависит только от counter (указан в массиве зависимостей), изменение counter2 не вызовет перезапуск эффекта. Эффект обновляет заголовок только когда меняется counter.

5. Ключевые выводы 💡

  • UseEffect для синхронизации: используйте этот хук для синхронизации вашего компонента с системами вне React.
  • Мыслите зависимостями: поведение useEffect полностью определяется массивом зависимостей.
  • Эффект запускается ПОСЛЕ рендера: пользователь сначала видит обновленный интерфейс, и потом выполняется код внутри useEffect.
  • Отсутствие зависимостей = запуск при каждом рендере: это правило приводит к лишним вычислениям и проблемам с производительностью.
  • Основная идея хуком useEffect — не просто «выполнить какой-то код после рендера», а синхронизация состояния React с внешним миром.

6. Важные нюансы и частые ошибки ⚠️

6.1. Почему React использует именно новую функцию?

Каждый раз мы передаем в useEffect новую функцию, чтобы она могла работать с актуальными данными.

6.2. Осторожно: горячая перезагрузка (Hot Reload)

Горячая перезагрузка может заставить useEffect сработать при сохранении файла в редакторе. Рекомендуется очищать кэш при отладке.

6.3. Правило: не меняйте состав зависимостей

React строго следит за стабильностью количества элементов в массиве зависимостей.

6.4. Как называть функцию внутри useEffect?

  • Callback-функция (callback) — общий термин для функции, передаваемой в другую функцию.
  • Setup-функция (setup) — «настраивает» эффект.
  • Эффект (effect) — название, используемое в официальных TypeScript-типах для React. Всё это относится к одной и той же функции.

7. Практический пример: загрузка данных по клику 🎧

jsx
import { useEffect, useState } from "react"
 
export function App() {
  const [selectedTrackId, setSelectedTrackId] = useState(null)
  const [selectedTrack, setSelectedTrack] = useState(null)
  const [tracks, setTracks] = useState(null)
 
  useEffect(() => {
    console.log("effect")
    fetch("https://musicfun.it-incubator.app/api/1", {
      headers: {
        "api-key": "xxx",
      },
    })
      .then((res) => res.json())
      .then((json) => setTracks(json.data))
  }, [])
 
  useEffect(() => {
    if (!selectedTrackId) {
      return
    }
 
    fetch("https://musicfun.it-incubator.app/api/1.0/playlists/tracks/" + selectedTrackId, {
      headers: { "api-key": "xxx" },
    })
      .then((res) => res.json())
      .then((json) => {
        setSelectedTrack(json.data)
      })
  }, [selectedTrackId])
 
  if (tracks === null) {
    return <div>Loading tracks...</div>
  }
 
  if (tracks.length === 0) {
    return <div>No tracks available</div>
  }
 
  return (
    <div>
      <h1>MusicFun</h1>
      <button
        onClick={() => {
          setSelectedTrackId(null)
          setSelectedTrack(null)
        }}
      >
        Reset selection
      </button>
 
      <div style={{ display: "flex", gap: "30px" }}>
        <ul>
          {tracks.map((track) => {
            return (
              <li
                key={track.id}
                style={{
                  border: track.id === selectedTrackId ? "1px solid orange" : "",
                }}
              >
                <div
                  onClick={() => {
                    setSelectedTrackId(track.id)
                  }}
                >
                  {track.attributes.title}
                </div>
                <audio src={track.attributes.attachments[0].url} controls></audio>
              </li>
            )
          })}
        </ul>
 
        <div>
          <h2>Details</h2>
          {selectedTrack === null ? (
            "Track is not selected"
          ) : (
            <div>
              <h3>{selectedTrack.attributes.title}</h3>
              <h4>Lyrics</h4>
              <p>{selectedTrack.attributes.lyrics ?? "no lyrics"}</p>
            </div>
          )}
        </div>
      </div>
    </div>
  )
}

Эффекты:

  • Первый useEffect используется для загрузки списка треков из API при монтировании компонента. Данные загружаются с помощью Fetch API, и полученные треки устанавливаются в состояние tracks.
  • Второй useEffect срабатывает каждый раз, когда изменяется selectedTrackId. Он загружает детали выбранного трека, используя его ID для нового запроса к API.
💡

Заключение: useEffect — это мощный инструмент для управления побочными эффектами. Ключ к его правильному использованию лежит в понимании его основной задачи — синхронизации.


🏠 Домашнее задание

Цель задания:

Сформировать глубокое понимание работы хука useEffect в React и научиться применять его для взаимодействия с внешним миром (API, браузерные API, таймеры).

1. Теория: закрепление основ

Ответь на вопросы:

  1. Что такое побочный эффект в React?
  2. Почему побочные эффекты нельзя вызывать прямо в теле компонента?
  3. Когда именно запускается функция из useEffect?

2. Поведение useEffect

Задание 2.1: Предскажи, что произойдет в этих случаях:

javascript
useEffect(() => {
  console.log("effect")
})
javascript
useEffect(() => {
  console.log("effect")
}, [])
javascript
useEffect(() => {
  console.log("effect", counter)
}, [counter])

Для каждого варианта опиши:

  • Сколько раз сработает эффект?
  • В какие моменты?

3. Практика с простыми компонентами

Создай компонент InputTitle, в котором:

  • Есть поле ввода (input), куда пользователь вводит текст.
  • С помощью useEffect сделай так, чтобы заголовок вкладки (document.title) всегда показывал введённое значение.
  • Пока ничего не ведено в input показывай текст по умолчанию ('Введите текст')
InputTitle.tsx
import { useState, useEffect } from "react"
 
export function InputTitle() {
  const [text, setText] = useState("")
 
  /*...*/
}

Итоговый результат 🚀

res15-

4. Практика на основе App.tsx

Продолжай работать в проекте с предыдущих домашних заданий.

Создай новый useEffect и вынеси в него логики из onClick при выборе задачи

🔶 При попытке вынеси логику в useEffect ты обнаружишь, что помимо selectedTaskId, еще нужно знать boardId 🤔

🔶 Поэтому в данной задаче нам придется добавить еще один useState для хранения boardId и при клике сетать новое значение boardId

App.tsx
import { useEffect, useState } from "react"
 
export function App() {
  const [selectedTaskId, setSelectedTaskId] = useState(null)
  const [selectedTask, setSelectedTask] = useState(null)
  const [tasks, setTasks] = useState(null)
  const [boardId, setBoardId] = useState(null)
 
  useEffect(() => {
    fetch("https://trelly.it-incubator.app/api/1.0/boards/tasks", {
      headers: {
        "api-key": "xxx",
      },
    })
      .then((res) => res.json())
      .then((json) => setTasks(json.data))
  }, [])
 
  useEffect(() => {
    // твой код
  }, [selectedTaskId])
 
  return (
    <div>
      {/*...*/}
      <div style={{ display: "flex", gap: "30px" }}>
        <div>
          {tasks.map((task) => {
            return (
              <div
                key={task.id}
                style={{
                  border: task.id === selectedTaskId ? "5px solid blue" : "5px solid black",
                  padding: "5px 10px",
                  marginBottom: "10px",
                  width: "300px",
                  background: task.attributes.priority >= 2 ? "#fba675" : "#ffffff",
                }}
                onClick={() => {
                  setSelectedTaskId(task.id)
                  fetch(
                    `https://trelly.it-incubator.app/api/1.0/boards/${task.attributes.boardId}/tasks/${task.id}`,
                    {
                      headers: {
                        "api-key": "xxx",
                      },
                    },
                  )
                    .then((res) => res.json())
                    .then((json) => {
                      setSelectedTask(json.data)
                    })
                }}
              >
                {/*...*/}
              </div>
            )
          })}
        </div>
        {/*...*/}
      </div>
    </div>
  )
}

Итоговый результат 🚀 Все должно работать так же как и до вынесения логики в useEffect


💡

🔶 Помни: каждая задача в этом ДЗ основана на реальных сценариях из веб-разработки.

🔶Экспериментируй: пробуй разные варианты зависимостей в useEffect и наблюдай за результатами.

🔶Удачи! Чем больше практики — тем ближе ты к уверенной работе с React. 🚀

Боевой маршрут (React Путь Самурая: без альтернатив)

Видеоурок - 17 видео из 30