От автора: при работе с React — и до выпуска хуков в версии 16.8 — всегда была возможность создавать компоненты тремя разными способами в зависимости от ваших потребностей.
1. Элементарные компоненты — эти компоненты являются простейшими, они характеризуются тем, что являются простыми переменными, которые хранят определенное выражение JSX, поэтому они не получают свойств и не имеют состояния, хотя они могут использовать любой оператор как обычно, например:
1 2 3 4 5 6 7 |
const componentname = list.length > 1 ? [ <li className="my-class">{list[0]}</li>, <li className="my-class">{list[1]}</li> ] : null; |
Этот массив, введенный в тег JSX <ul>, отобразит список в нашей DOM.
2. Функциональные компоненты. В до-хуковскую эру эти компоненты в основном использовались для изоляции потенциально повторно используемых функций, но с дополнительной логикой, не зависящей от их состояния, поскольку функциональные компоненты получали свойства, но не обладали состоянием. Примером функционального компонента может быть:
1 2 3 4 5 6 7 8 9 |
function MyComponent(props) { return <h1>This component returns: {props.value}</h1> } // We can render it directly const myComponent = <MyComponent value="Exactly this"/> ReactDOM.render( myComponent, document.getElementById('root') ); |
3. Компоненты класса — это всегда были наиболее распространенные компоненты в разработке React, потому что они были единственными со свойствами, состоянием и жизненным циклом, что делало их необходимыми для управления основной логикой и циклом приложения. Самый простой пример компонента класса с состоянием и с некоторым жизненным циклом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class MyComponent extends React.Component { constructor(props) { super(props); this.state = { value: '' } } componentDidMount() { this.setState({ value: 'This one' }) } render() { const { value } = this.state; return <h1>This component returns: {value}</h1> } } |
Однако эти предположения оказались устаревшими, когда React представил хуки в 2018 году, обещая новый способ работы, основанный на функциональных компонентах с доступом к состоянию и жизненному циклу.
Что такое хук?
Хук — это функция javascript, которая позволяет создавать / получать доступ к состоянию и жизненным циклам React, и которая для обеспечения стабильности приложения должна использоваться в соответствии с двумя основными правилами:
Должна вызываться на верхнем уровне приложения — хук никогда не должен вызываться внутри циклов, условных выражений или вложенных функций, поскольку порядок вызова хуков всегда должен быть одинаковым, чтобы гарантировать предсказуемость результата во время рендеринга. Использование только верхнего уровня — это то, что гарантирует, что внутреннее состояние React правильно сохраняется между разными вызовами одного и того же хука.
Должен вызываться в функциях или других пользовательских обработчиках React — хук никогда не должен вызываться вне функции React или другого пользовательского обработчика, чтобы логика состояния компонента была четко видна из остальной части кода для области, установленной React.
Как мы видели в предыдущем разделе, функциональный компонент до React 16.8 не мог иметь состояния или жизненного цикла, поэтому единственный способ создать, например, простой аккумулятор:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Acumulador extends React.Component { constructor(props) { super(props); this.state = { count: 0 } } render() { const { count } = this.state; return ( <h1>The button has been clicked { count } times</h1> <button onClick={() => this.setState({count: count + 1})}>Click me</button> ) } } |
Однако благодаря хукам мы теперь можем реплицировать этот компонент, добавляя состояние с использованием useState, так что функция предыдущего аккумулятора будет:
1 2 3 4 5 6 7 |
function Acumulador(props) { const [count, setCount] = useState(0); return ( <h1>The button has been clicked { count } times</h1> <button onClick={() => setCount(count => count + 1)}>Click me</button> ); } |
Хуки в React
Поскольку конечная цель хуков — упростить реальную логику, React предоставляет сокращенный набор с гибкостью, позволяющей реагировать на различные ситуации в жизненном цикле приложения, а также с возможностью создавать свои собственные.
Базовые хуки
React предоставляет три основных хука, которые отвечают обычным потребностям реализации жизненного цикла в компоненте класса.
Хук состояния useState
Этот хук возвращает значение с поддерживаемым состоянием и функцию, которая необходима для его обновления:
1 |
const [count, setCount] = useState(0) |
Начальное состояние — это параметр, переданный в useState, в данном случае 0, и это будет состояние, по крайней мере, во время начального рендеринга и до тех пор, пока функция setCount не будет вызвана с новым значением, так что в этом случае, например, значение setCount(count + 1) будет увеличиваться, став 1 в следующем рендеринге.
Также можно использовать значение предыдущего состояния в самой функции установки состояния, поэтому приведенное выше также можно записать как setCount(count => count + 1).
Хук эффекта useEffect
Этот хук позволяет нам добавлять побочные эффекты к заданному функциональному компоненту, то есть он позволяет нам вносить изменения в нашу логику после выполнения рендеринга таким же образом, как методы жизненного цикла компонентов класса componentDidMount, componentDidUpdate и componentWillUnmount.
Одно из больших преимуществ этого хука состоит в том, что он упрощает жизненный цикл, так что три доступных метода класса могут быть выражены с использованием только этого хука. Например, в случае компонента класса, который загружает набор данных из файла cookie при монтировании и записывает его при размонтировании, мы могли бы написать его так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class LoadUnload extends React.Component { constructor(props) { super(props); this.state = { data: null } } componentDidMount() { // theoretical function that returns cookie data as a js object const loadedData = getCookie('data'); this.setState({data: loadedData}) } componentWillUnmount() { const { data } = this.state; // Theoretical function that saves the data object in JSON format setCookie(data); } } |
Это можно сделать с помощью одного функционального компонента:
1 2 3 4 5 6 7 8 9 |
function LoadUnload(props) { const [data, setData] = useState(null); useEffect(() => { const loadedData = getCookie('data'); setData(loadedData); return () => setCookie(data); }, []); } |
Где, используя пустой массив зависимостей, мы говорим, что эффект запускается только при монтировании (componentDidMount), а результат функции — это новая функция, которая вызывается только при размонтировании (componentWillUnmount).
Хук контекста useContext
Если вы когда-либо использовали контекст React, это то, что вам нужно. Контекст в React — это способ передачи данных между различными компонентами без необходимости вручную каскадировать свойства. Например, когда мы хотим создать темы или местоположения, которые должны быть глобальными для всего дерева компонентов, и может быть обременительным распространять их для каждого добавленного компонента.
В случае компонентов класса контекст передается через провайдер, который охватывает дерево компонентов, которые должны иметь этот контекст, так что компонент, который имеет локализацию и использует контекст LocalContext, может быть записан следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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; |
В случае функционального компонента и с использованием хуков мы можем использовать useContext, что позволяет нам получить доступ к созданному контексту, например, для небольшого приложения, которое передает местоположение компоненту, подобному предыдущему, но функционально:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
const local = { en: { title: 'Hi', paragraph: 'This is a little test' }, es: { title: 'Hola', paragraph: 'Esto es un pequeño ejemplo' } }; 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> ); } |
Дополнительные хуки
В дополнение к этим хукам, есть ряд хуков, ориентированных на оптимизацию нашего потока рендеринга, чтобы избежать потери циклов, таких как useCallback и useMemo, цель которых — запоминать функции и функциональные компоненты, чтобы не рендерить их бесполезно, если ни одна из их зависимостей не изменена (как когда мы реализуем жизненный цикл shouldComponentUpdate в компонентах класса).
Однако об этих и других хуках мы увидим больше информации об их применении в следующей части, где будет показан практический пример, где можно применить эти знания.
Зачем использовать хуки?
Использование хуков в функциональных компонентах кажется простым дополнением, которое, по сути, не заменяет и не предназначено для замены текущих компонентов класса, поэтому мы могли бы спросить себя: «В чем смысл использования хуков и изменения способа разработки с помощью React?»
Прежде всего, как сказал Дэн Абрамов в презентации этой функции, использование хуков сокращает количество концепций, необходимых при разработке приложений React, так что нам не нужно постоянно переключаться между функциями, классами или элементами, чтобы выполнять аналогичные задачи; хуки предлагают нам однородность в экосистеме.
Во-вторых, жизненный цикл React был значительно упрощен за счет использования хуков, так что, как мы видели ранее, методы жизненного цикла классов componentDidMount, componentDidUpdate и componentWillUnmount суммированы в одном хуке useEffect, который действует как все три. Рассмотрим таймер, который добавляет определенное количество в секунду, используя сначала компонент класса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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>Han pasado {this.state.seconds} segundos</div> ) } } |
Это можно выразить как функциональный компонент с использованием хука:
1 2 3 4 5 6 7 8 9 10 |
function Timer(props) { const [seconds, setSeconds] = useState(0); useEffect(() => { const interval = setInterval(() => { setSeconds(seconds => seconds + 1); }, 1000); return () => clearInterval(interval); },[]); return <div>Han pasado {seconds} segundos</div>; } |
Несомненно, он намного компактнее как по качеству кода, так и по использованию функций, да и в работе он похож.
В-третьих, использование функциональных компонентов усиливает основной принцип React по предотвращению мутаций, поскольку изменение состояния компонента класса аналогично изменению его свойства state, так же, как необходимось выполнить привязку для функций, управляющих событиями, подходов, которые значительно увеличивают сложность и снизить предсказуемость компонента.
И, наконец, введение хука useReducer вводит в ядро React возможность работы с шаблоном Redux без необходимости дополнительных зависимостей. Этот хук, занесенный в категорию «дополнительных хуков», всегда рекомендуется, когда состояние приложения слишком сложное и с большим количеством вложений, поскольку редуктор принимает функцию типа (состояние, действие) => newState, возвращающее текущее состояние и функция диспетчеризации, которая позволяет нам эмулировать функции, доступные в настоящее время в библиотеках redux и react-redux, которые так часто используются для решения проблемы управления состоянием или каскадирования свойств.
Продолжение
React Hooks предлагает нам новую парадигму и новый образ мышления, который тесно связан с функциональным программированием, где функциональные возможности являются предсказуемыми блоками ввода-вывода и считаются первоклассными в использовании, а побочные эффекты изолированы очень контролируемым образом.
Однако обладать теоретическими знаниями — это не значит владеть мастерством их применения, поэтому в следующей части, посвященной хукам, мы разработаем небольшую инкрементальную игру с использованием хуков, уделяя особое внимание потоку приложения, преобразованию в компоненты. и хуки для жизненного цикла, а также использование хуков оптимизации для повышения производительности (например, уже упомянутые useCallback и useMemo).
Автор: Janeth Kent
Источник: www.ma-no.org
Редакция: Команда webformyself.
Читайте нас в Telegram, VK, Яндекс.Дзен