Статьи по React

React Hooks: что это такое и какие проблемы они решают?

Работая с React, и до выхода Hooks в версии 16.8, у вас всегда была возможность создавать компоненты тремя разными способами:

Элементарные компоненты.

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

const component = list.length > 1 ? [ <li className="mi-clase">{list[0]}</li>, <li className="mi-clase">{list[1]}</li> ] : null;
Code language: PHP (php)

Данный массив, вставленный в тег JSX, выведет список в DOM.

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

function MyComponent (props) { return <h1>Компонент выводит на экран: {props.value}</h1> } // Мы можем сделать это напрямую const MyComponent = <MyComponent value="То же самое"/> ReactDOM.render( miComponente, document.getElementById('root') );
Code language: JavaScript (javascript)

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

class MyComponent extends React.Component { constructor(props) { super(props); this.state = { value: '' } } componentDidMount() { this.setState({ value: 'То же самое' }) } render() { const { value } = this.state; return <h1>Компонент выводит на экран: {value}</h1> } }
Code language: JavaScript (javascript)

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

Что такое хук?

Хук – это функция javascript, которая позволяет создавать/ получать доступ к состоянию и жизненным циклам React.

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

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

Как мы видели в предыдущем разделе, функциональный компонент до версии React 16.8 не мог иметь состояния или жизненного цикла, поэтому единственным способом создания, например, простого аккумулятора было:

class Acumulador extends React.Component { constructor(props) { super(props); this.state = { count: 0 } } render() { const { count } = this.state; return ( <h1>Кнопка была нажата { count } раз</h1> <button onClick={() => this.setState({count: count + 1})}>Нажми на меня</button> ) } }
Code language: JavaScript (javascript)

Благодаря хукам мы можем использовать этот компонент, добавив состояние с помощью useState, поэтому предыдущий аккумулятор в функциональном виде будет выглядеть так:

function Acumulador(props) { const [count, setCount] = useState(0); return ( <h1>Кнопка была нажата { count } раз</h1> <button onClick={() => setCount(count => count + 1)}>Нажми на меня</button> ); }
Code language: JavaScript (javascript)

Хуки в React

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

Базовые хуки

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

Хук состояния useState

Этот хук возвращает значение с сохраненным состоянием и функцию, необходимую для его обновления:

const [count, setCount] = useState(0)
Code language: JavaScript (javascript)

Начальное состояние – это параметр, переданный в useState, в данном случае 0, и это будет состояние, доступное во время начального рендеринга и до вызова функции setCount с новым значением. В данном случае, например, setCount(count + 1) увеличит значение count на единицу, которая станет 1 при следующем рендеринге. Также можно использовать значение предыдущего состояния в самой функции установки состояния, поэтому вышеописанное также можно записать как setCount(count => count + 1).

Хук эффекта useEffect

Этот хук позволяет нам добавлять дополнительные эффекты к заданному функциональному компоненту, то есть позволяет вносить изменения в нашу логику после выполнения рендеринга, аналогично методам жизненного цикла componentDidMount, componentDidUpdate и componentWillUnmount в компонентах класса.

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

class LoadUnload extends React.Component { constructor(props) { super(props); this.state = { data: null } } componentDidMount() { // функция, возвращающая данные cookie в виде js-объекта const loadedData = getCookie('data'); this.setState({data: loadedData}) } componentWillUnmount() { const { data } = this.state; // функция, сохраняющая объект данных в формате JSON setCookie(data); } }
Code language: JavaScript (javascript)

Это может быть сделано с помощью одного функционального компонента:

function LoadUnload(props) { const [data, setData] = useState(null); useEffect(() => { const loadedData = getCookie('data'); setData(loadedData); return () => setCookie(data); }, []); }
Code language: JavaScript (javascript)

В этом случае, используя пустой массив зависимостей, мы говорим процессу запускаться только при подключении (например, componentDidMount). А возвратом функции является новая функция, которая вызывается только при отключении (например, componentWillUnmount).

Хук контекста useContext

Если вы уже использовали контекст React, это хук для вас. Контекст в React – это способ передачи данных между различными компонентами без необходимости ручного каскадирования props. Это полезно, например, когда мы хотим создать темы или локализации, которые должны быть глобальными для всего дерева компонентов.

В случае компонентов класса контекст передается через provider, который охватывает дерево компонентов. Эти компоненты должны содержать соответствующий контекст, так что компонент, имеющий локализацию и использующий контекст LocalContext, может быть написан следующим образом:

import {LocalContext} from './local-context'; class LocalizedText extends React.Component { render() { let local = this.context; return ( <div {...this.props} > <h1>{local.title}</h1> <p>{local.paragraph}</p> </div> ); } } LocalizedText.contextType = LocalContext; export default LocalizedText;
Code language: JavaScript (javascript)

В случае функционального компонента и с использованием хуков, мы можем использовать useContext, который позволяет нам получить доступ к созданному контексту. Например, для небольшого приложения, которое передает местоположение компоненту, подобному предыдущему, но в функциональном виде:

const local = { es: { title: 'Здравствуйте', paragraph: 'Это тест' }, en: { title: 'Hello', paragraph: 'This is a test' } }; const LocalContext = React.createContext(local.es); function App() { return ( <ThemeContext.Provider value={local.en}> <Container /> </ThemeContext.Provider> ); } function Container(props) { return ( <div> <LocalizedText /> </div> ); } function LocalizedText(props) { const local = useContext(LocalContext); return ( <div {...props}> <h1>{local.title}</h1> <p>{local.paragraph}</p> </div> ); }
Code language: JavaScript (javascript)

Зачем использовать хуки?

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

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

Во-вторых, жизненный цикл React был значительно упрощен с помощью хуков. Так что, как мы видели ранее, методы жизненного цикла классов componentDidMount, componentDidUpdate и componentWillUnmount обобщены в одном хуке useEffect, который действует как все три.

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

class Timer extends React.Component { constructor(props) { super(props); this.state = { seconds: 0 } } componentDidMount() { const {seconds, quantityToAdd} = this.state; this.interval = setInterval(() => { this.setState({seconds: seconds + 1}) }) } componentWillUnmount() { clearInterval(this.interval); } render() { return ( <div>Прошло {this.state.seconds} секунд.</div> ) } }
Code language: JavaScript (javascript)

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

function Timer(props) { const [seconds, setSeconds] = useState(0); useEffect(() => { const interval = setInterval(() => { setSeconds(seconds => seconds + 1); }, 1000); return () => clearInterval(interval); }, []); return <div>Прошло {seconds} секунд.</div>; }
Code language: JavaScript (javascript)

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

В-третьих, использование функциональных компонентов усиливает основной принцип React – избегать мутаций. Поскольку изменение состояния компонента класса – это то же самое, что мутировать его свойство state. Так же как и необходимость выполнять привязку для функций, управляющих событиями, подходы, которые значительно увеличивают сложность и снижают предсказуемость компонента.

И наконец, мы видим, что введение хука useReducer вводит в ядро React возможность работы с паттерном Redux без необходимости использования дополнительных зависимостей. Этот хук рекомендуется, когда приложение слишком сложное и с большим количеством вложенности. Так как reducer принимает функцию типа (state, action) => newState, возвращающую текущее состояние, и функцию “dispatch”, которая позволяет нам эмулировать функциональность, доступную в настоящее время в библиотеках redux и react-redux, используемых для решения проблемы управления состоянием или каскадирования свойств.

Заключение.

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

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *