React Hooks и WebPython
React Hooks — это долгожданные дополнения в React 16.8, с радостью встреченные сообществом React. Они являются полностью совместимыми с обычными подходами написания компонент.
Перед тем как рассматривать, что это такое, вспомним немного, как можно писать компоненты React.
import React from 'react'; class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
Данный компонент является тем, что называется stateless, — у него отсутствует, что называется state, — любые данные, которые свойственны конкретному компоненту, которые меняются по собственному желанию.
Такие stateless-компоненты можно переписать в стрелочном виде:
import React from 'react'; const Welcome = ({name}) => <h1>Hello, {name}</h1>;
Как можно увидеть, писать такие stateless-компоненты в стрелочном виде — огромное удовольствие. Но что делать с компонентами, которые имеют состояние? Например, с таким:
import React from 'react'; class Example extends React.Component { // Да, класс написан без наворотов babel — это для лучшего понимания ;) constructor(props) { super(props); this.state = { count: 0 }; } render() { const { count } = this.state; return ( <div> <p>You clicked {count} times</p> <button onClick={ () => this.setState({count: count + 1}) }> Click me </button> </div> ); } }
Обратите внимание, что для того, чтобы реализовать подобное состояние, необходимо: - проинициализировать начальное значение в конструкторе; - хранить этот count в state; - получать значение этого count в render; - и писать всё время this.setState(...), чтобы вызывать re-render при изменении state.
И это ещё простой пример! В действительности код может быть куда сложнее, как минимум, с вынесенным методом изменения состояния.
Разберёмся, как этот компонент переписать с помощью React Hooks:
import React, { useState } from 'react'; const Example = () => { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
Обратите внимание, компонент написан в виде стрелочной функции.
useState
Итак, useState:
// Данная функция принимает начальное значение поля и возвращает специальный массив ... = useState(0);
Что возвращает:
// useState возвращает массив из двух элементов — текущее состояние поля стейта // а ещё специальный метод — чтобы изменять состояние этого поля const [count, setCount] = useState(0);
Обратите внимание, что мы можем называть переменные для значения и метода, как хотим:
// Это всё корректно const [count, setCount] = useState(0); const [countValue, changeCount] = useState(0); const [c, updateCounter] = useState(0); const [userName, setUserName] = useState('Masha');
А дальше пользоваться этой переменной и методом можно вот таким образом:
<!-- Вот здесь просто вывели значение переменной --> <p>You clicked {count} times</p> <!-- А вот здесь просто вызываем в обработчике --> <button onClick={() => setCount(count + 1)}>
А что будет, если мы напишем несколько хуков?
const [count, setCount] = useState(0); const [userName, setUserName] = useState('Masha');
Как ни странно, state не склеит эти два поля, и они будут изолированы. Как useState определит, что это два разных поля? Это просто — по порядку вызова. Ну, т. е. первый вызов useState вернёт значение и метод для “первого” поля, второй — значение и методы для “второго” и т. д.
Поэтому вводятся логичные практики: 1. Использовать хуки в корне компонента. 2. Не использовать хуки в циклах.
Собственно, всё. Подведём промежуточные итоги: 1. Мы теперь можем писать компоненты со state и в стрелочном виде. 2. useState инициализирует поле state начальным значением. 3. useState берёт на себя обязанности по хранению поля state. 4. useState оборачивает вызов setState и предоставляет более удобный метод для изменения значения. Поэтому компонент обновится.
Да и, разумеется, хуки нужно использовать только в функциональных компонентах.
useEffect и другие
Но и это не всё!
Если мы хотим делать что-то дополнительное при обновлении компонента, то классический способ выглядел бы следующим образом:
import React from 'react'; class Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } // вызываем callback на начальном значении componentDidMount() { const { onNewValue } = this.props; onNewValue(this.state); } // вызываем callback при изменении значения componentDidupdate() { const { onNewValue } = this.props; onNewValue(this.state); } render() { const { count } = this.state; return ( <div> <p>You clicked {count} times</p> <button onClick={ () => this.setState({count: count + 1}) }> Click me </button> </div> ); } }
Обратите внимание, что помимо длинного кода, мы ещё и получили необходимость перегружать два метода: componentDidMount чтобы обновить данные на первом рендере и componentDidUpdate чтобы обновить данные при последующих рендерах.
Всё это можно объединить, используя хук useEffect:
import React, { useState } from 'react'; const Example = ({onNewValue}) => { const [count, setCount] = useState(0); // Функция в аргументе вызовется на componentDidMount и componentDidUpdate useEffect(() => onNewValue(count)); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
useEffect — это и есть способ задать componentDidMount и componentDidUpdate разом.
Помимо всего прочего — useRef, useReducer, useContext, useCallback и useMemo. Ну и оставим читателям возможность самостоятельно изучить, как писать собственные хуки.