Откройте React Hooks с анимацией и реальным боем (1): useState и useEffect

React.js
Откройте React Hooks с анимацией и реальным боем (1): useState и useEffect

Мы разработали инструмент с открытым исходным кодом для написания технических руководств на основе Git. Все руководства в нашем сообществе Tuque написаны с использованием этого инструмента. Добро пожаловатьStarой

Если вы хотите быстро понять, как его использовать, добро пожаловать в нашУчебная документацияой

Эта статья была написана членами сообщества Tuque.mRcнаписано, присоединяйтесьСообщество Туке, чтобы вместе создавать замечательные бесплатные технические руководства, чтобы способствовать развитию индустрии программирования.

Если вы считаете, что мы хорошо поработали, помнитеНравится + Подписаться + КомментарийСанлиан, поощряй нас писать лучшие уроки 💪

С момента выпуска React 16.8 React Hooks с ним вызвали необратимый шторм во фронтенд-круге. React Hooks предоставляют неограниченную функциональность для функциональных компонентов и устраняют многие присущие классовым компонентам ловушки. Этот урок поможет вам быстро ознакомиться с двумя наиболее часто используемыми хуками и освоить их:useStateа такжеuseEffect. Понимая, как его использовать, вы также можете увидеть принцип, лежащий в его основе, и, кстати, реализовать приложение для визуализации COVID-19 (новая коронная пневмония).

Добро пожаловать в этот проектРепозиторий GitHubа такжеGite-репозиторий.

Начало

Предварительные условия

Прежде чем читать этот урок, я надеюсь, что вы сделали следующие приготовления:

Зачем там крючки?

До появления хуков разделение труда между компонентами классов и компонентами функций было примерно таким:

  • компонент классаОбеспечивает полное управление состоянием и контроль жизненного цикла, обычно используемые для выполнения сложной бизнес-логики, известной как "умные компоненты"
  • функциональный компонентИсключительно из данных для представления карты, нет восприятия состояния, поэтому их обычно называют «Компонент для чайников"

Некоторые команды также разрабатывают соглашения о разработке компонентов React следующим образом:

Компоненты с состоянием не отображаются, а отображаемые компоненты не имеют состояния.

Итак, какую проблему решают хуки? Мы можем попытаться обобщить репрезентативность компонентов классаБолевые точки:

  1. Головная больthisУправление, легко ввести трудно отслеживаемые ошибки
  2. Разделение жизненного цикла не соответствует принципу «сплоченности», напримерsetIntervalа такжеclearIntervalЭта тесно связанная логика разделена на разные методы жизненного цикла.
  3. Дилемма повторного использования компонентов (совместное использование данных или повторное использование функций) от раннего Mixin доКомпоненты высшего порядка (HOC), затем кRender Propsникогда не существовало четкой, интуитивно понятной и простой в обслуживании схемы повторного использования компонентов.

Да, с запуском Hooks эти болевые точки ушли в историю!

Зачем писать эту серию руководств по хукам?

Как быстро изучить и освоить React Hooks всегда было проблемой, которая мучает многих новичков или старых игроков, и автор также обнаружил следующие головные боли в ежедневном обучении и разработке:

  • Официальная документация React объясняет хуки в частичном приложении, и принцип объясняется одним махом.
  • Есть много отличных статей о React Hooks, но большинство из них сосредоточено на объяснении одного или двух хуков, их сложно охватить за один раз.
  • Я прочитал много методов использования и даже анализ исходного кода, но я не могу сопоставить конкретные сценарии использования и не знаю, как гибко использовать их в реальной разработке.

Если у вас такое же замешательство, я надеюсь, что эта серия статей поможет вам развеять облака и сделать Крюки вашим предпочтительным оружием. Мы проведем полный проект визуализации данных COVID-19 в сочетании с принципом анимации Hooks, чтобы вы действительно освоили React Hooks!

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

Инициализировать проект

Сначала инициализируйте проект через Create React App (далее CRA):

npx create-react-app covid-19-with-hooks

Немного подождав, войдите в проект.

намекать

Все наши данные взяты изNovelCOVID 19 API, вы можете щелкнуть, чтобы получить доступ к полной документации API.

Все готово, поехали!

USESTATE + USEFFECT: Newbie

Во-первых, давайте начнем с двух наиболее часто используемых хуков:useStateа такжеuseEffect. Очень вероятно, что вы уже прикоснулись к нему и использовали его в своем обычном обучении и развитии (конечно, не беда, если вы только начинаете учиться). Но перед этим давайте познакомимся с тем, как работают функциональные компоненты React.

Понять процесс работы функциональных компонентов

Мы знаем, ХуксРаботает только с функциональными компонентами React. Поэтому понимание работы функциональных компонентов имеет решающее значение для понимания многих важных функций хуков. См. следующий рисунок:

Как видите, функциональные компоненты строго следуютUI = render(data)режим. Когда мы вызываем функцию компонента в первый раз, запускаемпервый рендер; затем сpropsизменения, функция компонента будет вызвана снова, вызываяперерисовать.

Вам может быть интересно, почему в анимации три одинаковых компонента нарисованы рядом? Потому что я хочу визуализировать важную идею функциональных компонентов таким образом:

Каждый рендер полностью независим.

Позже мы будем следовать этому стилю и шаг за шагом рассмотрим роль хуков в функциональных компонентах.

Анализ состояния использования

Сначала давайте просто понятьuseStateИспользование хуков описано в официальной документации следующим образом:

const [state, setState] = useState(initialValue);

вstateявляется переменной состояния,setStateявляется функцией Setter, которая изменяет состояние, иinitialValueявляется начальным значением состояния.

Просто смотреть на код может быть немного абстрактно, см. анимацию ниже:

По сравнению с предыдущими чисто функциональными компонентами мы вводимuseStateЭтот крюк сломался за мгновение доUI = render(data)Спокойная картина - функциональные компоненты могут на самом делеСостояние «ловушки» и функции изменения состояния извне компонента! И внимательно посмотрите на анимацию выше,Вызывая функцию Setter, вы также можете напрямую инициировать повторную визуализацию компонента.!

намекать

Вы могли заметить, что все «крючки» указывают на зеленый вопросительный знак, ниже мы подробно разберем, что это было, а теперь посмотрим, как временное дополнение к компонентам может получить доступ к «загадочному полю».

Комбинируя анимацию выше, мы можем сделать важный вывод:Каждый рендер имеет независимое значение состояния(Ведь каждый рендеринг полностью независим). То есть в каждой функцииstateПеременная — это простопостоянный, константа, полученная из хука каждый раз, когда он отображается, и нет никакой магии, такой как привязка данных.

Это старая поговоркаCapture Valueхарактеристика. Взгляните на следующий классический код счетчика (из книги Дэнаэта замечательная статья):

function Counter() {
  const [count, setCount] = useState(0);

  function handleAlertClick() {
    setTimeout(() => {
      alert('You clicked on: ' + count);
    }, 3000);
  }

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
      <button onClick={handleAlertClick}>
        Show alert
      </button>
    </div>
  );
}

После реализации вышеуказанного счетчика (вы также можете напрямую передать этоSandboxопыта), выполните следующие действия: 1) Нажмите кнопку «Нажми меня», чтобы увеличить число до 3; 2) Нажмите кнопку «Показать оповещение»; 3) ВsetTimeoutЩелкните Щелкнуть меня перед срабатыванием, чтобы увеличить число до 5.

Результатом является предупреждение, показывающее 3!

Если вы считаете этот результат нормальным, поздравляем, вы поняли идею Capture Value! Если вы думаете, что это странно... давайте кратко объясним:

  • Каждый рендеринг независим друг от друга, поэтому состояние, обработчики событий и т. д. в компоненте независимы каждый раз, когда он рендерится, илипринадлежат толькотот рендеринг
  • мы вcountСработало, когда было 3handleAlertClickфункция, эта функциявспомнил countтакже 3
  • Через три секунды функция только чтоsetTimeoutконец, выходвспомнил тогдаРезультат: 3

Это похоже на открытие дневника десятилетней давности: хотя вы открываете его сейчас, в нем все равно записано время десятилетней давности. Другими словами, дневник запечатлел это прекрасное воспоминание.

анализ использования useEffect

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

useEffect(effectFn, deps)

effectFnэто реализация, которая может иметьпобочный эффектФункции эффектов (такие как сбор данных, установка/уничтожение таймеров и т. д.), которые могут возвращатьфункция очистки(уборка), такая как всем знакомаяsetIntervalа такжеclearInterval:

useEffect(() => {
  const intervalId = setInterval(doSomething(), 1000);
  return () => clearInterval(intervalId);
});

Как видите, мы передаем тело функции EffectsetIntervalЗапускается таймер, а затем возвращается функция очистки, чтобы уничтожить только что созданный таймер.

Хорошо, это все еще звучит абстрактно, давайте посмотрим на анимацию ниже:

Анимация имеет следующие моменты, на которые следует обратить внимание:

  • Каждый Эффект должен выполняться после рендеринга, чтобы он не блокировал рендеринг и повышал производительность.
  • Перед запуском каждого эффекта запустите функцию очистки эффекта предыдущего рендера (если есть).
  • Когда компонент уничтожен, запустите функцию очистки последнего эффекта.

намекать

Отсрочка выполнения Эффекта до завершения рендеринга по соображениям производительности.Если вы хотите выполнить некоторую логику перед рендерингом (за счет производительности рендеринга), вы можете использоватьuseLayoutEffectкрючки, используйте те жеuseEffectТочно такие же, только сроки исполнения разные.

посмотри сноваuseEffectВторой параметр:deps(в зависимости от массива). Как видно из демонстрационной анимации выше, React будетЗапускайте эффект после каждого рендера. Массив зависимостей используется для управления тем, следует ли запускать эффект, что может сократить ненужные вычисления и оптимизировать производительность. В частности, пока каждый элемент в массиве зависимостей не изменился с момента последнего рендеринга, пропустите выполнение этого эффекта.

Подумав хорошенько, находимuseEffectПо сравнению с жизненным циклом компонентов предыдущего класса хуки имеют две примечательные характеристики:

  • рендерится с первого раза(componentDidMount), перерисовать (componentDidUpdate) и уничтожить (componentDidUnmount) Логика трех этапов решается единым API
  • Поместите всю связанную логику в Эффект (например,setIntervalа такжеclearInterval), подчеркивая связность логики

В самом крайнем случае можно указатьdepsпустой массив[], что гарантирует, что Эффектбудет выполняться только после первоначального рендеринга компонента. Фактическая анимация эффекта выглядит следующим образом:

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

Уведомление

Если вы знакомы с механизмом повторного рендеринга React, вы должны догадаться.depsМассивы также используются при оценке того, изменился ли элемент.Object.isСравнивать. Поэтому скрытая опасность заключается в том, что когдаdepsКогда один из элементов не является примитивным типом (например, функция, объект и т. д.),меняется каждый рендер, тем самым потерявdepsЗначение самого себя (условное срабатывание Эффекта). Далее мы объясним, как обойти эту дилемму.

настоящий бой

Хорошо, когда дело доходит до реального боя, мы сначала реализуем сбор глобального обзора данных (повторный сбор каждые 5 секунд). Создайтеsrc/components/GlobalStats.jsКомпонент, используемый для отображения глобального обзора данных, имеет следующий код:

import React from "react";

function Stat({ number, color }) {
  return <span style={{ color: color, fontWeight: "bold" }}>{number}</span>;
}

function GlobalStats({ stats }) {
  const { cases, deaths, recovered, active, updated } = stats;

  return (
    <div className='global-stats'>
      <small>Updated on {new Date(updated).toLocaleString()}</small>
      <table>
        <tr>
          <td>
            Cases: <Stat number={cases} color='red' />
          </td>
          <td>
            Deaths: <Stat number={deaths} color='gray' />
          </td>
          <td>
            Recovered: <Stat number={recovered} color='green' />
          </td>
          <td>
            Active: <Stat number={active} color='orange' />
          </td>
        </tr>
      </table>
    </div>
  );
}

export default GlobalStats;

можно увидеть,GlobalStatsПростой функциональный компонент без всяких хуков.

затем изменитьsrc/App.js, импортируя только что созданныйGlobalStatsкомпонент, код выглядит следующим образом:

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

import "./App.css";
import GlobalStats from "./components/GlobalStats";

const BASE_URL = "https://corona.lmao.ninja/v2";

function App() {
  const [globalStats, setGlobalStats] = useState({});

  useEffect(() => {
    const fetchGlobalStats = async () => {
      const response = await fetch(`${BASE_URL}/all`);
      const data = await response.json();
      setGlobalStats(data);
    };

    fetchGlobalStats();
    const intervalId = setInterval(fetchGlobalStats, 5000);

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

  return (
    <div className='App'>
      <h1>COVID-19</h1>
      <GlobalStats stats={globalStats} />
    </div>
  );
}

export default App;

Как видите, мыAppкомпонент, первый проходuseStateкрючок введенglobalStatsПеременные состояния и функции, изменяющие это состояние. затем пройтиuseEffectКрюк для получения данных API, есть следующие точки для примечания:

  1. Мы определяемfetchGlobalStatsАсинхронная функция и вызов для получения данных вместо прямого использования этой асинхронной функции какuseEffectпервый параметр;
  2. Создан интервал для обновления данных каждые 5 секунд;
  3. Возвращает функцию для интервала, созданную ранее уничтоженной.

Кроме того, второй параметр (массив зависимостей) является пустым массивом, поэтому вся функция Effect будет выполняться только один раз.

Уведомление

Иногда вы можете случайно написать Effect как асинхронную функцию:

useEffect(async () => {
  const response = await fetch('...');
  // ...
}, []);

Это нормально?Настоятельно не рекомендую этого делать.useEffectПо соглашению функция Effect либо не возвращает никакого значения, либо возвращает функцию очистки. И здесь асинхронная функция неявно вернет Promise, что напрямую нарушает это соглашение и приведет к непредсказуемым результатам.

Наконец, прикрепите глобальный файл CSS приложения, код выглядит следующим образом (просто скопируйте и вставьте):

.App {
  width: 1200px;
  margin: auto;
  text-align: center;
}

.history-group {
  display: flex;
  justify-content: center;
  width: 1200px;
  margin: auto;
}

table,
th,
td {
  border: 1px solid #ccc;
  border-collapse: collapse;
}

th,
td {
  padding: 5px;
  text-align: left;
}

.global-stats > table {
  margin: auto;
  margin-top: 0.5rem;
  margin-bottom: 1rem;
}

пройти черезnpm startОткройте проект:

Кроме того, вы можете проверить вкладку «Сеть» консоли, и вы должны увидеть, что наше приложение каждые пять секунд отправляет запрос на получение последних данных. Поздравляем, вы успешно выполнили сбор данных с помощью Hooks!

useState + useEffect: становится лучше

На предыдущем шаге мы былиAppСостояние и эффект определены в компоненте, но фактическое приложение не может быть таким простым.Как правило, требуется несколько состояний и эффектов.Как понять и использовать это в настоящее время?

Углубленный характер умирания

В анимации в предыдущем разделе мы видели, что каждый раз, когда компонент визуализируется, мы можем «зацепить» состояние с помощью волшебного крючка, но у нас есть знак вопроса, откуда берутся эти крючки. Теперь пришло время разгадать тайну.

Уведомление

Следующие анимации не совсем соответствуют исходному коду реализации React Hooks, но это хороший способ помочь вам понять, как это работает. Конечно, это также может помочь вам съесть настоящий исходный код.

Давайте сначала посмотрим, что происходит при первом рендеринге (монтировании) компонента:

Обратите внимание на следующие моменты:

  1. На первом рендере мы передаемuseStateОпределено несколько состояний;
  2. каждый звонокuseState, создаст запись Hook вне компонента, включая значение состояния (сuseStateинициализация с заданным начальным значением) и функция Setter, изменяющая состояние;
  3. несколько вызововuseStateСгенерированная запись Hook формируетсвязанный список;
  4. вызыватьonClickфункция обратного вызова, вызовsetS2модификация функцииs2, не только изменяет значение состояния в записи ловушки, но ивызвать повторный рендеринг.

ОК, время рендеринга, анимация выглядит следующим образом:

Видно, что связанный список записей Hook все еще существует после первоначального рендеринга и до повторного рендеринга. когда мы звоним один за другимuseStateкогда,useStateОн возвращает состояние, хранящееся в связанном списке Hook, и сеттер, который изменяет состояние.

намекать

Когда вы полностью разберетесь в двух приведенных выше анимациях, вы сможете понять, почему этот хук называетсяuseStateвместоcreateStateДа почему так называетсяuse, поскольку он создается, когда недоступен (при первом рендеринге), а иногда читается напрямую (при повторном рендеринге).

Проведя приведенный выше анализ, мы можем легко найти, чтоuseStateИзысканность в дизайне (от Чжан Лили:Некоторые мысли о React Hooks):

  • Состояние и сеттер-функция, модифицирующая состояние, парные, причем последняя должна воздействовать на первую, на первую влияет только вторая, и в целом на них совершенно не влияет внешний мир
  • Поощряет мелкозернистое и плоское определение состояния и контроль, которые имеют большое значение для предсказуемости и тестируемости поведения кода.
  • КромеuseState(и другие хуки), функциональные компоненты по-прежнему являются «чистыми» компонентами, которые реализуют логику рендеринга, а управление состоянием инкапсулировано хуками.

Глубокое погружение в суть useEffect

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

Обратите внимание на некоторые из этих деталей:

  1. useStateа такжеuseEffectдобавляется в список крючков каждый раз, когда он называется;
  2. useEffectДополнительно добавит функцию Эффекта, ожидающую выполнения в очереди;
  3. После завершения рендеринга каждая функция Effect в очереди Effect вызывается по очереди.

К этому моменту так же выявился жизненный опыт двух "вопросительных знаков" в анимации предыдущего раздела - простосвязанный списокВот и все! Оглядываясь назад, мы вспоминаем момент, подчеркнутый в официальной документации React Rules of Hooks:

Вызовите хуки только на верхнем уровне.

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

Не лгите: дело в Депсе

useEffect(включая другие подобныеuseCallbackа такжеuseMemoи т. д.) имеют массив зависимостей (deps), интересная особенность этого параметра заключается в том, что решение об указании зависимостей полностью зависит от вас. Можно конечно выбрать "солгать" и дать пустую не смотря ни на чтоdepsМассив, как будто поговорка «Эта функция эффекта не имеет зависимостей, доверять мне».

Однако этот вид ленивого подхода, очевидно, приведет к всем видам ошибках. Вообще говоря, используетсяpropилиstateследует добавить кdepsв массив. Кроме того, React официально запустил специальныйПлагин ESLint, что может помочь вам исправить это автоматическиdepsмножество (Честно говоря, иногда автоматический фикс этого плагина был совсем отстойным......).

настоящий бой

Начиная с этого шага, мы будем использоватьRechartsКак библиотека диаграмм для визуальных приложений, она обеспечивает отличный уровень связывания для D3 и React. Добавьте его с помощью следующей командыrechartsполагаться:

npm install recharts

Создайтеsrc/components/CountriesChart.jsИспользуется для демонстрации гистограмм связанных данных в нескольких странах. Код выглядит следующим образом:

import React from "react";
import {
  BarChart,
  CartesianGrid,
  XAxis,
  YAxis,
  Tooltip,
  Legend,
  Bar,
} from "recharts";

function CountriesChart({ data, dataKey }) {
  return (
    <BarChart
      width={1200}
      height={250}
      style={{ margin: "auto" }}
      margin={{ top: 30, left: 20, right: 30 }}
      data={data}
    >
      <CartesianGrid strokeDasharray='3 3' />
      <XAxis dataKey='country' />
      <YAxis />
      <Tooltip />
      <Legend />
      <Bar dataKey={dataKey} fill='#8884d8' />
    </BarChart>
  );
}

export default CountriesChart;

Создайтеsrc/components/SelectDataKey.js, используемый для выбора отображаемых ключевых индикаторов, код выглядит следующим образом:

import React from "react";

function SelectDataKey({ onChange }) {
  return (
    <>
      <label htmlFor='key-select'>Select a key for sorting: </label>
      <select id='key-select' onChange={onChange}>
        <option value='cases'>Cases</option>
        <option value='todayCases'>Today Cases</option>
        <option value='deaths'>Death</option>
        <option value='recovered'>Recovered</option>
        <option value='active'>Active</option>
      </select>
    </>
  );
}

export default SelectDataKey;

SelectDataKeyИспользуется, чтобы позволить пользователям выбирать следующие ключевые показатели:

  • cases: Совокупное количество подтвержденных случаев
  • todayCases: Подтвержденные случаи сегодня
  • deaths: Совокупное количество смертей
  • recovered: Количество вылеченных
  • active: Количество существующей диагностики

Наконец мы в корневом компонентеsrc/App.jsВведите два компонента, созданные выше, код выглядит следующим образом:

// ...
import GlobalStats from "./components/GlobalStats";
import CountriesChart from "./components/CountriesChart";
import SelectDataKey from "./components/SelectDataKey";

const BASE_URL = "https://corona.lmao.ninja/v2";

function App() {
  const [globalStats, setGlobalStats] = useState({});
  const [countries, setCountries] = useState([]);
  const [key, setKey] = useState("cases");

  useEffect(() => {
    // ...
  }, []);

  useEffect(() => {
    const fetchCountries = async () => {
      const response = await fetch(`${BASE_URL}/countries?sort=${key}`);
      const data = await response.json();
      setCountries(data.slice(0, 10));
    };

    fetchCountries();
  }, [key]);

  return (
    <div className='App'>
      <h1>COVID-19</h1>
      <GlobalStats stats={globalStats} />
      <SelectDataKey onChange={(e) => setKey(e.target.value)} />
      <CountriesChart data={countries} dataKey={key} />
    </div>
  );
}

export default App;

можно увидеть:

  1. Создаем два новых состоянияcountries(данные по всем странам) иkey(Индикаторы для сортировки данных - пять выше);
  2. Мы прошлиuseEffectХук для сбора данных аналогичен предыдущему сбору глобальных данных, но обратите внимание, что второй параметр (массив зависимостей) на нашей стороне равен[key], то есть только тогда, когдаkeyКогда состояние изменится, оно будет вызваноuseEffectфункция внутри.
  3. Наконец, используйте два созданных ранее подкомпонента и передайте соответствующие данные и функции обратного вызова.

Запустив проект, вы увидите, что гистограмма показывает данные первой десятки стран, и вы можете изменить показатели сортировки (например, вы можете начать с кумулятивного диагноза по умолчанию).casesпереключиться на список погибшихdeaths):

Это выглядит очень хорошо!

На этом первая часть этой серии закончена, я надеюсь, вы действительно понимаетеuseStateа такжеuseEffect- Два наиболее часто используемых хука. существуетСледующий урок, мы продолжим работу с пользовательскими хуками иuseCallback.

использованная литература

Хотите узнать больше интересных практических технических руководств? ПриходитьСообщество ТукеМагазин вокруг.