Hooks-first підхід в React-розробці

  • 29 января, 17:00
  • 3933
  • 0

В останньому великому оновленні React отримав хуки - особливий спосіб доступу до API бібліотеки. Це дозволяє відмовитися від створення класових компонентів, використовуючи при цьому всі їхні чудові можливості (стан, обробка побічних ефектів). Завдяки хукам можна зберігати код програми чистіше і логічніше. В цьому довіднику ми сконцентруємося саме на них - hooks-first підхід в React-розробці.

Базові концепції

Елементи і JSX синтаксис

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

// JSX can use any valid html tags (i.e. div/span, h1-h6, form/input, etc)
<div>Hello React</div> 

JSX-елементи є виразами:

//as an expression, JSX can be assigned to variables...
const greeting = <div>Hello React</div>;

const isNewToReact = true;

// or can be displayed conditionally
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-теги:

// to write JSX on multiple lines, wrap in parentheses: ()
const greeting = (
  // div is the parent element
  <div>
    {/* h1 and p are child elements */}
    <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(), який зробить всю роботу по рендерингу за вас.

// imports needed if using NPM package; not if from CDN links
import React from "react";
import ReactDOM from "react-dom";

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

// ReactDOM.render(root node, mounting point)
ReactDOM.render(greeting, document.getElementById("root"));

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

Створення

У React є два основних способи оголошення компонента: функціональний і класовий. З першим все просто: компонент - це звичайна функція, яка повертає JSX-розмітку.

import React from "react";

// 1st component type: function componen
function Header() {
  return <h1>Hello React</h1>;
}

// function components with arrow functions are also valid
const Header = () => <h1>Hello React</h1>;

Класи мають трохи більше складний шаблон. Вони повинні наслідуватись від React.Component і реалізовувати метод render:

// 2nd component type: class component
// (classes are another type of function)
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";

// we add custom 'attributes' called props
//we called this prop 'username', but can use any valid JS identifier
ReactDOM.render(
  <Header username={username} />,
  document.getElementById("root")
);


// props is the object that every component receives as an argument
function Header(props) {
  return <h1>Hello {props.username}</h1>;
}

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

function Header(props) {
  // we cannot do the following with 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>
  );
}

Сам колбек не може бути асинхронним (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-шпаргалку по основних можливостях бібліотеки.

Джерело: proglib.io


0 комментариев
Сортировка:
Добавить комментарий