реагирующие хуки | Кодементор
# Реагировать на хуки
Крючки — новое дополнение в Реагировать 16.8. Они позволяют вам использовать состояние и другие функции React без написания компонента класса.
После этого добавления функциональный компонент больше не является компонентом без состояния. Теперь мы можем добиться поведения компонента, используя функцию, как мы привыкли иметь с крючками жизненного цикла компонента класса, такими как компонентдидмаунт, ComponentWillUnmount, компонентдидобдате и т.д.. Те, где обычно используется важный компонент, и мы можем добиться того же с помощью хука useEffect.
Нам часто приходилось поддерживать компоненты, которые изначально были простыми, но превратились в неуправляемый беспорядок логики с отслеживанием состояния и побочных эффектов. Каждый метод жизненного цикла часто содержит смесь несвязанной логики. Например, компоненты могут выполнять некоторую выборку данных в componentDidMount и componentDidUpdate. Однако тот же метод componentDidMount может также содержать некоторую несвязанную логику, которая настраивает прослушиватели событий с очисткой, выполняемой в componentWillUnmount. Взаимно связанный код, который изменяется вместе, разделяется на части, но совершенно несвязанный код в конечном итоге объединяется в один метод. Это делает слишком легким внесение ошибок и несоответствий.
Во многих случаях невозможно разбить эти компоненты на более мелкие, потому что везде присутствует логика с отслеживанием состояния. Их также трудно проверить. Это одна из причин, по которой многие люди предпочитают комбинировать React с отдельной библиотекой управления состоянием. Однако это часто приводит к слишком большой абстракции, требует переключения между разными файлами и затрудняет повторное использование компонентов.
Чтобы решить эту проблему, хуки позволяют разделить один компонент на более мелкие функции в зависимости от того, какие части связаны между собой (например, настройка подписки или выборка данных), а не форсировать разделение на основе методов жизненного цикла. Вы также можете выбрать управление локальным состоянием компонента с помощью редуктора, чтобы сделать его более предсказуемым. Ниже приведены списки добавленных хуков:
Основные крючки
- использование состояния
- использованиеЭффект
- использованиеконтекста
Дополнительные крючки
- использоватьОбратный звонок
- использовать памятку
- использованиередьюсер
- useRef
- useImperativeHandle
- использоватьLayoutEffect
- использоватьDebugValue
использование состояния
Возвращает значение с сохранением состояния и функцию для его обновления. Если новое состояние вычисляется с использованием предыдущего состояния, вы можете передать функцию в setState или обновленное значение. В случае функции — она получит предыдущее значение и вернет обновленное значение. Вот пример компонента счетчика, который использует обе формы setState:
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</>
);
}
Давайте преобразуем компонент класса в функциональный компонент, чтобы представить имя и фамилию в форме. Ниже приведен компонент класса UserForm.
import React, { Component } from "react";
class UserForm extends Component {
constructor(props) {
super(props);
this.state = {
firstName: "",
lastName: ""
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
render() {
const { firstName, lastName } = this.state;
return (
<form>
<input
value={firstName}
onChange={this.handleInputChange}
placeholder="First name"
type="text"
name="firstName"
required
/>
<input
value={lastName}
onChange={this.handleInputChange}
placeholder="Last name"
type="text"
name="lastName"
required
/>
<button type="submit">Submit</button>
</form>
);
}
}
export default UserForm;
Эквивалентный функциональный компонент для вышеуказанного компонента класса может быть записан следующим образом:
import React, { useState } from "react";
import "./styles.css";
function UserForm() {
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
return (
<form>
<input
value={firstName}
onChange={e => setFirstName(e.target.value)}
placeholder="First name"
type="text"
name="firstName"
required
/>
<input
value={lastName}
onChange={e => setLastName(e.target.value)}
placeholder="Last name"
type="text"
name="lastName"
required
/>
<button type="submit">Submit</button>
</form>
);
}
export default UserForm;
По сути, мы объявляем переменную состояния и функцию, позволяющую позже изменить переменную состояния. Пустая строка в вызове useState является начальным значением firstName и может быть установлена в любое необходимое значение. Сейчас мы установим его в пустую строку.
Обратите внимание, что вы можете назвать функцию setFirstName как хотите. Однако принято добавлять «set» перед именем изменяемой переменной состояния.
использованиеЭффект
useEffect предназначен для обработки любого вида «побочного эффекта» (внесение изменений в какую-либо внешнюю систему, вход в консоль, выполнение HTTP-запроса и т. д.), который вызывается изменением данных вашего компонента или в ответ на рендеринг компонента. Он заменяет componentDidMount, componentDidUnmount и componentDidReceiveProps или некоторый код, который запускается каждый раз при изменении вашего состояния. Может быть сложно понять нюансы его использования, но, поняв, когда он работает и как это контролировать, вам может стать немного легче обернуть голову.
Запускайте эффект при каждом рендере
import React, { useState, useEffect } from "react";
default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`The count is ${count}`);
});
return (
<div>
<p>Count is {count}</p>
<button
onClick={() => {
setCount(count + 1);
}}>
increase
</button>
</div>
);
}
Запустите эффект только один раз
Допустим, мы хотели, чтобы эффект запускался только один раз… думайте об этом как о замене для componentDidMount. Для этого пройдите [] в качестве второго аргумента useEffect:
import React, { useEffect } from "react";
default function Mounted() {
useEffect(() => {
console.log("mounted");
}, []);
return <div>This component has been mounted.</div>;
}
Запуск эффекта при изменении данных
Если вы действительно хотите запускать эффект только при изменении определенного значения… скажем, для обновления некоторого локального хранилища или запуска HTTP-запроса, вы захотите передать те значения, которые вы отслеживаете для изменений, в качестве 2-го аргумента. В этом примере имя пользователя будет записываться в локальное хранилище после каждого его обновления (инициируемого onChange ввода).
import React, { useState, useEffect } from "react";
default function Listen() {
const [name, setName] = useState("");
useEffect(
() => {
localStorage.setItem("name", name);
},
[name]
);
return (
<div>
<input
type="text"
onChange={e => {
setName(e.target.value);
}}
value={name}
/>
</div>
);
}
Очистка от вашего эффекта
Иногда вам нужно отменить то, что вы сделали… чтобы убрать за собой, когда компонент должен быть размонтирован. Для этого вы можете вернуть функцию из функции, переданной в useEffect… это многословно, но давайте посмотрим на реальный пример того, что и componentDidMount, и componentDidUnmount будут объединены в один эффект.
import React, { useEffect } from "react";
default function Listen() {
useEffect(() => {
const listener = () => {
console.log("I have been resized");
};
window.addEventListener("resize", listener);
return () => {
window.removeEventListener("resize", listener);
};
}, []);
return <div>resize me</div>;
}
Избегайте установки состояния для несмонтированных компонентов
Поскольку эффекты запускаются после завершения рендеринга компонента и поскольку они часто содержат асинхронный код, возможно, что к тому времени, когда асинхронный код разрешится, компонент уже даже не будет смонтирован! Когда дело дойдет до вызова функции setData для обновления состояния, вы получите сообщение об ошибке, что вы не можете обновить состояние несмонтированного компонента.
Способ, которым мы можем решить указанную выше проблему (без каламбура), заключается в использовании локальной переменной и использовании функции «очистки», возвращаемой из нашей функции эффекта. Начав с значения true, мы можем переключить его на false, когда эффект будет очищен, и использовать эту переменную, чтобы определить, хотим ли мы по-прежнему вызывать функцию setData или нет.
import React, { useState, useEffect } from "react";
import Axios from "axios";
default function Fetcher({ url }) {
const [data, setData] = useState(null);
useEffect(
() => {
// Start it off by assuming the component is still mounted
let mounted = true;
const loadData = async () => {
const response = await Axios.get(url);
// We have a response, but let's first check if component is still mounted
if (mounted) {
setData(response.data);
}
};
loadData();
return () => {
// When cleanup is called, toggle the mounted variable to false
mounted = false;
};
},
[url]
);
if (!data) {
return <div>Loading data from {url}</div>;
}
return <div>{JSON.stringify(data)}</div>;
}
использованиеконтекста
useContext упрощает использование контекста. Типичный способ использования Context API выглядит следующим образом:
import React from "react";
import ReactDOM from "react-dom";
// Create a Context
const NumberContext = React.createContext();
// It returns an object with 2 values:
// { Provider, Consumer }
function App() {
// Use the Provider to make a value available to all
// children and grandchildren
return (
<NumberContext.Provider value={42}>
<div>
<Display />
</div>
</NumberContext.Provider>
);
}
function Display() {
// Use the Consumer to grab the value from context
// Notice this component didn't get any props!
return (
<NumberContext.Consumer>
{value => <div>The answer is {value}.</div>}
</NumberContext.Consumer>
);
}
ReactDOM.render(<App />, document.querySelector("#root"));
Видите, как мы получаем значение внутри компонента Display? Мы должны обернуть наш контент в NumberContext.Consumer и использовать шаблон реквизита рендеринга, передавая функцию как дочерний элемент, чтобы получить значение и отобразить его.
Это прекрасно работает, и «реквизиты рендеринга» — отличный шаблон для передачи динамических данных, но он приводит к ненужной вложенности и некоторым когнитивным накладным расходам (особенно если вы к этому не привыкли).
Давайте перепишем Display, используя новый хук useContext, и посмотрим, как это выглядит:
// import useContext (or we could write React.useContext)
import React, { useContext } from 'react';
// ...
function Display() {
const value = useContext(NumberContext);
return <div>The answer is {value}.</div>;
}
useCallback против useMemo
API-интерфейсы useCallback и useMemo выглядят одинаково. Они оба принимают функцию и массив зависимостей.
useCallback(fn, deps);
useMemo(fn, deps);
Так в чем же разница? useCallback возвращает свою функцию без вызова, поэтому вы можете вызвать ее позже, в то время как useMemo вызывает свою функцию и возвращает результат.
Вы хотите использовать useCallback или useMemo всякий раз, когда вы зависите от ссылочное равенство между рендерами. В React функции, определенные в функциональных компонентах, воссоздаются при каждом рендеринге из-за закрытия. И поэтому, если вы используете функцию как зависимость в useEffect, это приведет к бесконечному циклу. Поэтому, чтобы избежать этого, мы должны использовать useCallback. Ниже приведен отличный пост, объясняющий значение ссылочного равенства, подчеркивающий разницу между usecallback-vs-usememo.