28 января 2020

Настольный справочник по базовым принципам React

Frontend-разработчик в Foquz. https://www.cat-in-web.ru/
React – самый популярный инструмент для фронтенд-разработки. Подробно рассматриваем основные концепции и учимся применять их на практике.
Настольный справочник по базовым принципам React

В последнем большом обновлении React получил хуки – особый способ доступа к API библиотеки. Это позволяет отказаться от создания классовых компонентов, используя при этом все их замечательные возможности (состояние, обработка побочных эффектов). Благодаря хукам можно сохранять код приложения чище и логичнее. В этом справочнике мы сконцентрируемся именно на них – hooks-first подход в React-разработке.

Базовые концепции

Если вы только начинаете знакомиться с React, загляните в наши вводные материалы:

Элементы и JSX синтаксис

Простейший React-элемент выглядит как обычный HTML. Это возможно благодаря особому JSX-синтаксису (JavaScript XML):

        // можно использовать все валидные html-теги  (div/span, h1-h6, form/input, ...)
<div>Hello React</div> 
    

JSX-элементы являются выражениями:

        // их можно положить в переменную
const greeting = <div>Hello React</div>;

const isNewToReact = true;

// или вернуть из функции
function sayGreeting() {
  if (isNewToReact) {
    return greeting; // будет выведено: Hello React
  } else {
    return <div>Hi again, React</div>;
  }
}

    

Внутри JSX также можно использовать выражения. Для их интерполяции используются фигурные скобки:

        const year = 2020;
const greeting = <div>Hello React in {year}</div>;

const months = ['March', 'April', 'May'];
const spring = <div>{months}</div>

    

Таким образом, можно выводить любые примитивные значения и массивы, но попытка вставить сюда объект приведет к ошибке.

В один JSX-элемент можно вкладывать другие, как в обычные HTML-теги:

        // многострочный JSX следует оборачивать в круглые скобки ()
const greeting = (
  // родительский элемент
  <div>
    {/* дочерние элементы */}
    <h1>Hello!</h1>
    <p>Welcome to React</p>
  </div>
);

    

Особенности JSX

Несмотря на сходство с HTML, JSX все-таки им не является. Это расширение языка JavaScript, которое просто позволяет удобнее создавать DOM-элементы.

В JSX есть особенности. Например, все теги должны быть закрыты, а пустой тег можно закрыть сразу же:

        // Пустой div в HTML: <div></div> 
// Пустой div в JSX:
<div/>

// input в HTML: <input>
// input в JSX: 
<input name="email" />



    

Имеются различия в атрибутах. Например, вместо class нужно использовать className. Все имена атрибутов пишутся в camelCase, как JS-переменные.

        <button className="submit-button">Submit</button>
    

Для создания простого React-проекта нужно всего три вещи:

  1. DOM-элемент, внутри которого будет располагаться все приложение. В этой роли может выступать обычный div;
  2. Корневой JSX-элемент, который вы поместите в этот div;
  3. Метод ReactDOM.render(), который сделает всю работу по рендерингу за вас.
        // можно импортировать библиотеку с помощью менеджера пакетов
// или подключить из CDN
import React from "react";
import ReactDOM from "react-dom";

const greeting = <h1>Hello React</h1>;

// Метод принимает JSX-элемент и корневой DOM-элемент для рендера
ReactDOM.render(greeting, document.getElementById("root"));

    

Компоненты и пропсы

Создание

В React есть два основных способа объявления компонента: функциональный и классовый. С первым все просто: компонент – это обычная функция, которая возвращает JSX-разметку.

        import React from "react";

// Первый способ: функциональный компонент
function Header() {
  return <h1>Hello React</h1>;
}

// Можно использовать стрелочные функции
const Header = () => <h1>Hello React</h1>;



    

Классы имеют чуть более сложный шаблон. Они должны наследовать от React.Component и реализовывать метод render:

        // Второй способ: классовый компонент
// (классы в JS - это тоже функции)
class Header extends React.Component {
  render() {
    return <h1>Hello React</h1>;
  }
}
    

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

Использование

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

        const Header = () => <h1>Hello React</h1>;

ReactDOM.render(<Header />, document.getElementById("root"));
// на странице получим: <h1>Hello React</h1>

    

Компоненты в приложении можно использовать многократно как обычные теги. Например, Header можно выводить на разных страницах:

        // Домашняя страница по адресу '/'
function IndexPage() {
  return (
    <Header />
    <Hero />
    <Footer />
  );
}

// Страница About по адресу '/about'
function AboutPage() {
  return (
    <Header />
    <About />
    <Testimonials />
    <Footer />
  );
}

    

Передача данных

Кроме того, в компоненты при объявлении можно динамически передавать данные. Например, в Header можно передать имя авторизованного пользователя:

        const username = "John";

// данные передаются в кастомных атрибутах (props)
// для имени атрибута можно использовать любой валидный идентификатор
ReactDOM.render(
  <Header username={username} />,
  document.getElementById("root")
);


// Каждый компонент получает объект пропсов как аргумента
function Header(props) {
  return <h1>Hello {props.username}</h1>;
}

    

Пропсы нельзя изменять (мутировать) внутри компонента, они предназначены только для чтения. В идеале React-компоненты должны быть "чистыми" функциями (pure functions). Входные данные таких функций не должны изменяться.

        function Header(props) {
  // так делать нельзя
  props.username = "Doug";

  return <h1>Hello {props.username}</h1>;
}

    

Если параметр должен изменяться внутри компонента, скорее всего, это не свойство (prop), а состояние (state). Мы разберемся с ним чуть позже, в разделе useState.

В компонент можно вложить один или несколько дочерних элементов/компонентов. Чтобы получить к ним доступ, нужно обратиться к свойству props.children.

        // Компонент выводит все, что было в него вложено
function Layout(props) {
  return <div className="container">{props.children}</div>;
}

// Это удобно для создания компонентов-оберток
function IndexPage() {
  return (
    <Layout>
      <Header />
      <Hero />
      <Footer />
    </Layout>
  );
}

// На другой странице у компонента Layout может быть совсем другой набор "детей"
function AboutPage() {
  return (
    <Layout>
      <About />
      <Footer />
    </Layout>
  );
}

    

Условный рендер

Чтобы вывести (или не вывести) компонент по какому-либо условию, в JSX нужно воспользоваться тернарным оператором. Обычный if-else здесь не подойдет, так как интерполировать в JSX можно только выражения, то есть конструкции, которые возвращают какой-либо результат.

        function Header() {
  const isAuthenticated = checkAuth();

  return (
    <nav>
      <Logo />
      {/* Если юзер авторизован, выводим AuthLinks, иначе - Login  */}
      {isAuthenticated ? <AuthLinks /> : <Login />}
      {/* Если юзер авторизован, выводим Greeting */}
      {isAuthenticated && <Greeting />}
    </nav>
  );
}

    

Фрагменты

В JSX есть одно непреложное требование – из каждого выражения должен возвращаться только один родительский компонент/элемент. Поэтому, чтобы вернуть несколько элементов, расположенных на одном уровне, приходится оборачивать их в лишний div.

Фрагменты решают эту проблему. По сути, это DocumentFragment. При вставке в DOM он оставляет дочерние элементы, а сам растворяется, как будто его и не было.

        // если юзер авторизован, выведем два одноуровневых компонента 
function Header() {
  const isAuthenticated = checkAuth();

  return (
    <nav>
      <Logo />
      {isAuthenticated ? (
        <>
          <AuthLinks />
          <Greeting />
        </>
      ) : (
        <Login />
      )}
    </nav>
  );
}

    

Простейший синтаксис <></> – это удобное сокращение для компонента <React.Fragment>.

Списки и ключи

Мы не можем использовать цикл for, чтобы вывести сразу несколько элементов в JSX, так как он не возвращает значение. Но мы можем вместо этого воспользоваться итерирующими методами массивов!

Array.prototype.map поможет превратить массив данных в массив JSX-элементов, который легко можно интерполировать в разметку.

        const people = ["John", "Bob", "Fred"];
const peopleList = people.map(person => <p>{person}</p>);

    

.map() можно использовать и для вставки набора React-компонентов:

        function App() {
  const people = ['John', 'Bob', 'Fred'];
  
  return (
    <ul>
      {/* Каждому компоненту передается в свойстве name соответствующее имя */}
      {people.map(person => <Person name={person} />}
    </ul>
  );
}

function Person({ name }) {
  // достаем name с помощью деструктурирующего присваивания
  return <p>this person's name is: {name}</p>;
}

    

Важно! Каждый элемент/компонент в массиве должен иметь уникальное свойство key (уникальное только в пределах этого массива). Ключ необходим, чтобы React мог отслеживать каждый элемент.

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

        function App() {
  const people = ['John', 'Bob', 'Fred'];

  return (
    <ul>
      {/* ключи должны быть примитивными значениями */}
      {people.map(person => <Person key={person} name={person} />)}
    </ul>
  );
}


function App() {
  const people = ['John', 'Bob', 'Fred'];

  return (
    <ul>
      {/* использование порядкового номера в качестве ключа */}
      {people.map((person, i) => <Person key={i} name={person} />)}
    </ul>
  );
}

    

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

События и обработчики событий

Обработка событий в React немного отличается от HTML.

        // Название функций-обработчиков часто начинается с префикса handle
function handleToggleTheme() {
  // code to toggle app theme
}

// HTML
// атрибут onclick пишется в нижнем регистре
// в качестве значения указан ВЫЗОВ функции-обработчика
<button onclick="handleToggleTheme()">
  Submit
</button>

// JSX
// имя атрибута onClick указано в camelCase, так же как пропсы
// значением является ссылка на функцию-обработчик (без вызова)
<button onClick={handleToggleTheme}>
  Submit
</button>

    

Чаще всего в React вы будете использовать события onClick и onChange.

  1. onClick отслеживает клики по элементам, аналогично onclick в HTML;
  2. onChange обрабатывает изменения полей ввода (как oninput).
        function App() {
  function handleChange(event) {
    // обработчик получает объект события в качестве аргумента при вызове
    const inputText = event.target.value;
    const inputName = event.target.name; // myInput
  }

  function handleSubmit() {
    // при отправке формы объект события обычно не требуется
  }

  return (
    <div>
      <input type="text" name="myInput" onChange={handleChange} />
      <button onClick={handleSubmit}>Submit</button>
    </div>
  );
}

    

Хуки React

Состояние компонента и useState

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

        import React from 'react';

// создаем переменную для хранения состояния
// используя деструктуризацию массива
// синтаксис: const [stateVariable] = React.useState(defaultValue);
function App() {
  const [language] = React.useState('javascript');
  return <div>I am learning {language}</div>;
}

    

Примечание: все хуки, рассматриваемые в этом разделе, можно отдельно импортировать из пакета react.

        import React, { useState } from "react";

function App() {
  const [language] = useState("javascript");
  return <div>I am learning {language}</div>;
}

    

Обновление состояния

Разумеется, созданное состояние можно обновить, иначе не имело бы смысла его создавать. Для этого используются функции-сеттеры:

        function App() {
  // сеттер идет вторым параметром в деструктурируемом массиве
  // для именования используется префикс set + имя переменной в PascalCase
  const [language, setLanguage] = React.useState("python");

  return (
    <div>
      <button onClick={() => setLanguage("javascript")}>
        Change language to JS
      </button>
      <p>I am now learning {language}</p>
    </div>
  );
}

    

Чтобы обновить состояние, мы просто передаем в сеттер его новое значение.

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

Внутри одного компонента хук useState можно использовать несколько раз:

        function App() {
  const [language, setLanguage] = React.useState("python");
  const [yearsExperience, setYearsExperience] = React.useState(0);

  return (
    <div>
      <button onClick={() => setLanguage("javascript")}>
        Change language to JS
      </button>
      <input
        type="number"
        value={yearsExperience}
        onChange={event => setYearsExperience(event.target.value)}
      />
      <p>I am now learning {language}</p>
      <p>I have {yearsExperience} years of experience</p>
    </div>
  );
}

    

В useState в качестве состояния можно передать примитивное значение или даже объект.

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

        function App() {
  const [developer, setDeveloper] = React.useState({
    language: "",
    yearsExperience: 0,
    isEmployed: false
  });

  function handleToggleEmployment(event) {
    // передаем в сеттер функцию
    // в качестве параметра получаем текущее состояние
    // возвращаем обновленное состояние
    setDeveloper(prevState => {
      return { ...prevState, isEmployed: !prevState.isEmployed };
    });
  }

  return (
    <button onClick={handleToggleEmployment}>Toggle Employment Status</button>
  );
}

    

Побочные эффекты и хук useEffect

Хук useEffect позволяет выполнять из функционального компонента действия, которые вызывают побочные эффекты, например, получение данных с сервера, установка слушателей событий или взаимодействие с DOM-деревом.

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

        // выбираем цвет из массива
// и устанавливаем его в качестве фона страницы
function App() {
  const [colorIndex, setColorIndex] = React.useState(0);
  const colors = ["blue", "green", "red", "orange"];

  // работа с DOM API из компонента - это побочный (сторонний) эффект
  useEffect(() => {
    document.body.style.backgroundColor = colors[colorIndex];
  });
  // теперь при любом изменении состояния запустится перерендер
  // и вызовется эффект-функция

  function handleChangeIndex() {
    const next = colorIndex + 1 === colors.length ? 0 : colorIndex + 1;
    setColorIndex(next);
  }

  return <button onClick={handleChangeIndex}>Change background color</button>;
}

    

Чтобы избежать вызова эффект-функции после каждого рендера, можно передать в хук второй аргумент – пустой массив.

        function App() {
  ...
  // теперь, сколько бы мы ни кликали, цвет не поменяется
  useEffect(() => {
    document.body.style.backgroundColor = colors[colorIndex];
  }, []);
  // эффект-функция отработала только при первом монтировали

  return (
    <button onClick={handleChangeIndex}>
      Change background color
    </button>
  );
}

    

Что это за массив? Просто коллекция аргументов, при изменении которых должна отработать наша эффект-функция. Если массив пуст, то она сработает лишь единожды. Если мы положим туда, к примеру, colorIndex, то при каждом его изменении будет меняться фон страницы. Но если перерендер вызван чем-то другим, то коллбэк хука не будет вызван.

        function App() {
  const [colorIndex, setColorIndex] = React.useState(0);
  const colors = ["blue", "green", "red", "orange"];

  // говорим react, что вызывать эффект нужно только при изменении colorIndex
  useEffect(() => {
    document.body.style.backgroundColor = colors[colorIndex];
  }, [colorIndex]);

  function handleChangeIndex() {
    const next = colorIndex + 1 === colors.length ? 0 : colorIndex + 1;
    setColorIndex(next);
  }

  return <button onClick={handleChangeIndex}>Change background color</button>;
}

    

Хук useEffect также позволяет выполнить какие-то действия при изменении компонента. Здесь можно, например, отписаться от прослушки событий DOM, чтобы не тратить память.

Нужно просто вернуть из эффект-функции другую функцию, и React вызовет ее при перерендере или удалении компонента.

        function MouseTracker() {
  const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });

  React.useEffect(() => {
    // устанавливаем слушатель
    window.addEventListener("mousemove", event => {
      const { pageX, pageY } = event;
      setMousePosition({ x: pageX, y: pageY });
    });

    // удаляем слушатель при изменении компонента
    return () => {
      window.removeEventListener("mousemove", event => {
        const { pageX, pageY } = event;
        setMousePosition({ x: pageX, y: pageY });
      });
    };
  }, []);

  return (
    <div>
      <h1>The current mouse position is:</h1>
      <p>
        X: {mousePosition.x}, Y: {mousePosition.y}
      </p>
    </div>
  );
}
    
  1. Fetching data with useEffect

Сам коллбэк не может быть асинхронным (async), поэтому различные асинхронные операции нужно обрабатывать прямо внутри него или вынести в отдельную функцию:

        const endpoint = "https://api.github.com/users/codeartistryio";

// пример с промисами
function App() {
  const [user, setUser] = React.useState(null);

  React.useEffect(() => {
    fetch(endpoint)
      .then(response => response.json())
      .then(data => setUser(data));
  }, []);
}

// пример с async/await
function App() {
  const [user, setUser] = React.useState(null);
  React.useEffect(() => {
    getUser();
  }, []);

  // используем отдельную асинхронную функцию
  async function getUser() {
    const response = await fetch("https://api.github.com/codeartistryio");
    const data = await response.json();
    setUser(data);
  }
}

    

Производительность и хук useCallback

Хук useCallback используется для улучшения производительности компонентов за счет мемоизации функций обратного вызова.

При частом обновлении постоянное пересоздание обработчиков – это дорогое удовольствие. useCallback позволяет изменять их только в случае реальной необходимости – когда изменяются связанные с ними зависимости.

        // В таймере мы постоянно пересчитываем дату 
// при этом компонент каждый раз рендерится заново
// инкрементный счетчик при этом не увеличивается,
// но обработчик события onClick пересоздается, это нехорошо

function Timer() {
  const [time, setTime] = React.useState();
  const [count, setCount] = React.useState(0);

  // но если обернуть его в useCallack, 
  // он не будет изменяться без необходимости 
  const inc = React.useCallback(
    function handleIncrementCount() {
      setCount(prevCount => prevCount + 1);
    },
    // второй аргумент - массив зависимостей, как у хука useEffect
    [setCount]
  );

  React.useEffect(() => {
    const timeout = setTimeout(() => {
      const currentTime = JSON.stringify(new Date(Date.now()));
      setTime(currentTime);
    }, 300);

    return () => {
      clearTimeout(timeout);
    };
  }, [time]);

  return (
    <div>
      <p>The current time is: {time}</p>
      <p>Count: {count}</p>
      <button onClick={inc}>+</button>
    </div>
  );
}

    

Мемоизация и хук useMemo

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

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

Хук useMemo возвращает результат вычисления.

        function App() {
  // создаем состояние компонента
  const [wordIndex, setWordIndex] = useState(0);
  const [count, setCount] = useState(0);

  // будем рассчитывать длину слов в этом массиве
  const words = ["i", "am", "learning", "react"];
  const word = words[wordIndex];

  function getLetterCount(word) {
    // представим, что это невероятно тяжелая операция,
    // которая занимает уйму времени
    let i = 0;
    while (i < 1000000) i++;
    return word.length;
  }

  // Мемоизация сложных вычислений
  // позволяет значительно увеличить их производительность
  const letterCount = React.useMemo(() => getLetterCount(word), [word]);

  // если бы мы не использовали хук, вот так
  // const letterCount = getLetterCount(word);
  // то счетчик обновлялся бы с большой задержкой

  function handleChangeIndex() {
    // переходим к следующему слову
    const next = wordIndex + 1 === words.length ? 0 : wordIndex + 1;
    setWordIndex(next);
  }

  return (
    <div>
      <p>
        {word} has {letterCount} letters
      </p>
      <button onClick={handleChangeIndex}>Next word</button>
      <p>Counter: {count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

    

Рефы и хук useRef

Рефы – это специальные атрибуты, доступные всем React-компонентам. Они позволяют создать ссылку на компонент (или HTML-элемент) после того, как он появится в DOM.

Хук useRef – это простой способ создавать рифы внутри функциональных компонентов. Он возвращает значение, которое можно привязать к любому элементу, чтобы на него можно было ссылаться.

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

        function App() {
  const [query, setQuery] = React.useState("react hooks");
  // передаем хук дефолтное значение null
  const searchInput = useRef(null);

  function handleClearSearch() {
    // используем созданную ссылку в обработчике
    searchInput.current.value = "";
    searchInput.current.focus();
  }

  return (
    <form>
      <input
        type="text"
        onChange={event => setQuery(event.target.value)}
        ref={searchInput}
      />
      <button type="submit">Search</button>
      <button type="button" onClick={handleClearSearch}>
        Clear
      </button>
    </form>
  );
}
    

Продвинутые хук

Контекст и хук useContext

В React существует проблема передачи свойств целевым компонентам. Обычно мы поднимаем данные по дереву компонентов, чтобы хранить их в одном месте. Но затем их приходится спускать вниз по цепочке пропсов для вывода на страницу. Иногда несколько уровней компонентов просто передают вниз ненужные им данные. чтобы они достигли цели.

        function App() {
  // храним данные пользователя в App
  // но выводим в Header
  const [user] = React.useState({ name: "Fred" });

  return (
   {/* Первый уровень передачи данных через незаинтересованный компонент Main */}
    <Main user={user} />
  );
}

const Main = ({ user }) => (
  <>
    {/* Второй уровень передачи данных */}
    <Header user={user} />
    <div>Main app content...</div>
  </>
);

const Header = ({ user }) => <header>Welcome, {user.name}!</header>;

    

Чтобы избежать этого, можно воспользоваться React-концепцией контекста. Это общая область видимости для целого дерева компонентов.

        // Создаем контекст для данных юзера
const UserContext = React.createContext();

function App() {
  // Создаем состояние для хранения данных юзера
  const [user] = React.useState({ name: "Fred" });

  return (
    {/* Оборачиваем родительский компонент в провайдер контекста */}
    {/* Теперь данные юзера доступны всем дочерним компонентам */}
    <UserContext.Provider value={user}>
      <Main />
    </UserContext.Provider>
  );
}

const Main = () => (
  <>
    <Header />
    <div>Main app content...</div>
  </>
);

// Вместо пропсов используем UserContext.Consumer
const Header = () => (
  <UserContext.Consumer>
    {user => <header>Welcome, {user.name}!</header>}
  </UserContext.Consumer>
);

    

С хуком useContext все становится еще проще:

        const Header = () => {
  // Помещаем контекст в переменную user
  const user = React.useContext(UserContext);
  // Избавляемся от обертки UserContext.Consumer
  return <header>Welcome, {user.name}!</header>;
};

    

Редьюсеры и хук useReducer

Редьюсеры (или редукторы) – это простые чистые (предсказуемые) функции, которые получают в качестве аргументов предыдущее состояние объекта и объект действия (action), а возвращают обновленное состояние. Другими словами, редьюсеры применяют к состоянию некоторое действие.

        function reducer(state, action) {
  // действия редьюсера зависят от типа действия (экшена)
  switch (action.type) {
    // если action.type равен 'LOGIN'
    case "LOGIN":
      // устанавливаем имя юзера и флаг авторизации
      return { username: action.payload.username, isAuth: true };
    case "SIGNOUT":
      return { username: "", isAuth: false };
    default:
      // если действие имеет неизвестный тип, возвращаем текущее состояние
      return state;
  }
}

    

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

Этот хук можно использовать вместе с useContext, чтобы с легкостью передавать данные заинтересованным компонентам.

Вот маленький пример полноценной системы управления состоянием, основанной на связке useReducer + useContext.

        const initialState = { username: "", isAuth: false };

function reducer(state, action) {
  switch (action.type) {
    case "LOGIN":
      return { username: action.payload.username, isAuth: true };
    case "SIGNOUT":
      return { username: "", isAuth: false };
    default:
      return state;
  }
}

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  
  function handleLogin() {
    dispatch({ type: "LOGIN", payload: { username: "Ted" } });
  }

  function handleSignout() {
    dispatch({ type: "SIGNOUT" });
  }

  return (
    <>
      Current user: {state.username}, isAuthenticated: {state.isAuth}
      <button onClick={handleLogin}>Login</button>
      <button onClick={handleSignout}>Signout</button>
    </>
  );
}

    

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

Создание пользовательских хуков

Хуки создаются для того, чтобы можно было разделять одинаковое поведение между разными компонентами. Они работают гораздо очевиднее, чем компоненты высшего порядка или рендер-пропсы, которые нам приходилось использовать раньше.

React позволяет создавать кастомные хуки – и это очень здорово!

        // создаем хук для получения данных из API
function useAPI(endpoint) {
  const [value, setValue] = React.useState([]);

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

  async function getData() {
    const response = await fetch(endpoint);
    const data = await response.json();
    setValue(data);
  };

  return value;
};

// рабочий пример использования нового хука
function App() {
  const todos = useAPI("https://todos-dsequjaojf.now.sh/todos");

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

    

Правила хуков

В React есть два базовых правила использования хуков, которые обязательно нужно соблюдать:

  1. Хуки можно вызывать только из верхнего уровня вашего компонента. Не следует обращаться к ним из блоков условий, циклов или вложенных функций.
  2. Хуки можно вызывать только из функциональных компонентов. Внутри обычных JS-функций или классовых компонентов их использовать не следует.
        function checkAuth() {
  // Нарушено второе правило! 
  // Не используйте хуки в обычных функциях 
  React.useEffect(() => {
    getUser();
  }, []);
}

function App() {
  // правильное использование
  const [user, setUser] = React.useState(null);

  // Нарушено первое правило!
  // Не используйте хуки в условиях и циклах
  if (!user) {
    React.useEffect(() => {
      setUser({ isAuth: false });
    }, []);
  }

  checkAuth();

  // Нарушено первое правило!
  // Не используйте хуки во вложенных функциях
  return <div onClick={() => React.useMemo(() => doStuff(), [])}>Our app</div>;
}

    

Что ещё почитать?

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

Чтобы полезная информация всегда была под рукой, скачайте PDF-шпаргалку по основным возможностям библиотеки.

Источники

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ