Hooks

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

Hooks не работают внутри классов - они позволяют использовать React без классов.

Важно

Хуки позволяют вам повторно использовать логику с состоянием без изменения иерархии компонентов. Это позволяет легко обмениваться хуками между многими компонентами или сообщест

Почитать про базовые хуки useState и useEffect вы можете тут.

useRef

const ref = React.useRef();

useRef возвращает изменяемый ref-объект, свойство .current которого инициализируется переданным аргументом (initialValue). Возвращённый объект будет сохраняться в течение всего времени жизни компонента.

function Focus() {
const textInput = React.useRef();
const focusTextInput = () => textInput.current.focus();
return (
<div>
<input type="text" ref={textInput} />
<button onClick={focusTextInput}>Focus the text input</button>
</div>
);
}

useRef это контейнер, который может содержать изменяемое значение в своём свойстве .current.

note

Хук useRef() полезен не только установкой атрибута с рефом как вы привыкли использовать его в классовых компонентов но и удобен для сохранения любого мутируемого значения, по аналогии с тем, как вы используете поля экземпляра в классах.

useRef() создаёт обычный JavaScript-объект. Единственная разница между useRef() и просто созданием самого объекта {current: ...} — это то, что хук useRef даст один и тот же объект с рефом при каждом рендере.

important

useRef не уведомляет вас, когда изменяется его содержимое. Мутирование свойства .current не вызывает повторный рендер.

useReducer

useReducer – хук, похожий на useState, но дающий больший контроль над управлением состояния. Он принимает функцию редюсер и начальное состояние в качестве аргументов, а возвращает состояние и метод dispatch.

const [state, dispatch] = useReducer(reducer, initialArg, init);

Редюсер (так называется из-за типа функции, который передается методу array - Array.prototype.reduce(reducer, initialValue)) это шаблон взятый из Redux.

important

Хук useReducer обычно предпочтительнее useState, когда у вас сложная логика состояния, которая включает в себя несколько значений, или когда следующее состояние зависит от предыдущего.

useReducer также позволяет оптимизировать производительность компонентов, которые запускают глубокие обновления, поскольку вы можете передавать dispatch вместо колбэков.

Gример счётчика c использованием useReducer:

const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = React.useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
</div>
);
}
Примечание

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

Указание начального состояния

Существует два разных способа инициализации состояния useReducer. Вы можете выбрать любой из них в зависимости от ситуации. Самый простой способ — передать начальное состояние в качестве второго аргумента:

const [state, dispatch] = useReducer(reducer, { count: initialCount });
Примечание

React не использует соглашение об аргументах state = initialState, популярное в Redux. Начальное значение иногда должно зависеть от пропсов и поэтому указывается вместо вызова хука. Если вы сильно в этом уверены, вы можете вызвать useReducer(reducer, undefined, reducer), чтобы эмулировать поведение Redux, но это не рекомендуется.

useCallback

Предназначен для возврата мемоизованного каллбека.

const memoizedCallback = React.useCallback(() => {
doSomething(a, b);
}, [a, b]);

Хук useCallback вернёт мемоизированную версию колбэка, который изменяется только, если изменяются значения одной из зависимостей. Это полезно при передаче колбэков оптимизированным дочерним компонентам, которые полагаются на равенство ссылок для предотвращения ненужных рендеров (например, shouldComponentUpdate).

Примеры:

Хорошо

// SearchList
function SearchList({ handleClick, query }) {
const items = useSearch(query);
const renderItem = (item) => <div onClick={handleClick}>{item}</div>;
return <div>{items.map(renderItem)}</div>;
}
export default React.memo(SearchList);
// App
function App({ query }) {
const handleClick = React.useCallback(
(item) => {
console.log("Clicked by", item);
},
[query]
);
return <SearchList query={query} handleClick={handleClick} />;
}

handleClick мемоизирован функцией useCallback(). Пока переменная query остается неизменной, useCallback() возвращает тот же экземпляр функции.

Даже если по какой-то причине компонент MyParent выполняет повторный рендеринг, handleClick остается прежним и не нарушает мемоизацию SearchList.

Плохо 🛑

function App() {
const onClick = React.useCallback(() => {
// ваш код
}, []);
return <Button onClick={onClick} />;
}
function Button({ onClick }) {
return <button onClick={onClick}>Click me</button>;
}

Есть ли смысл запоминать onClick?

Нет, потому что для вызова useCallback() требуется много работы. Каждый раз, когда App отображается, вызывается хук useCallback().React гарантирует возврат той же функции. Даже в этом случае onClick функция по-прежнему создается при каждом рендеринге (useCallback() просто пропускает ее). Даже если useCallback() возвращает тот же экземпляр функции, это не приносит никаких преимуществ.

Важно

Не забывайте о повышенной сложности кода. Вы должны синхронизировать работу useCallback () с тем, что вы используете внутри мемоизированного обратного вызова.

Оптимизация обходится дороже, чем отсутствие оптимизации.

useMemo

const memoizedValue = React.useMemo(() => computeExpensiveValue(a, b), [a, b]);

Возвращает мемоизированное значение. Хук вернет новое значение только при изменении значения одной из зависимостей.

И React.useMemo, и React.useCallback получают функцию в качестве первого аргумента и массив зависимостей в качестве второго.

React.useMemo(() => {
fooFunction();
}, [dependencies]);
React.useCallback(() => {
fooFunction();
}, [dependencies]);
Примечание

Основное отличие состоит в том, что React.useMemo вызовет функцию fooFunction и вернет ее результат, в то время как React.useCallback вернет функцию fooFunction, не вызывая ее.

Мы можем использовать React.useMemo, когда вычисляем дорогое значение, которое мы не хотим вычислять снова и снова, когда компонент повторно визуализируется.

Например:

const questions = [
{
id:1,
title:"How old are you?"
},
{
id:2,
title:"What is your name?"
}
]
function Question = ({id})=>{
const question = questions.find( q => q.id === id);
if(question){
return <div>{question.title}</div>
}
return null
}

Переменная question будет вычисляться каждый раз когда компонент Question будет перерисован.

Мы можем воспользоваться хуком React.useMemo что бы обойти это:

const questions = [
{
id:1,
title:"How old are you?"
},
{
id:2,
title:"What is your name?"
}
]
function Question = ({id})=>{
const question = React.useMemo(() => questions.find( q => q.id === id),[id] );
if(question){
return <div>{question.title}</div>
}
return null
}

Teперь question будет вычисляться только если id будет изменен.

Примечание

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

useContext

const value = useContext(MyContext);

Принимает объект контекста (значение, возвращённое из React.createContext) и возвращает текущее значение контекста для этого контекста.

Текущее значение контекста определяется пропом value ближайшего <MyContext.Provider> над вызывающим компонентом в дереве.

Когда ближайший <MyContext.Provider> над компонентом обновляется, этот хук вызовет повторный рендер с последним значением контекста, переданным этому провайдеру MyContext.

Примечание

Даже если родительский компонент использует React.memo или реализует shouldComponentUpdate, то повторный рендер будет выполняться, начиная c компонента, использующего useContext.

Запомните, аргумент для useContext должен быть непосредственно сам объект контекста:

  • Правильно ✅: useContext(MyContext)
  • Неправильно 🛑: useContext(MyContext.Consumer)
  • Неправильно 🛑: useContext(MyContext.Provider)
Важно

Компонент, вызывающий useContext, всегда будет перерендериваться при изменении значения контекста. Если повторный рендер компонента затратен, вы можете оптимизировать его с помощью мемоизации.

const themes = {
light: {
foreground: "#000000",
background: "#eeeeee",
},
dark: {
foreground: "#ffffff",
background: "#222222",
},
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
Я стилизован темой из контекста!
</button>
);
}

Пользовательские Hooks

Одной из самых крутых особенностей Hooks является то, что вы можете легко делиться логикой между несколькими компонентами, создавая собственный Hook.

useToggle

import React from "react";
const useToggle = (initialState) => {
const [on, setOn] = React.useState(initialState);
const onToggle = React.useCallback(() => setOn((prev) => !prev), []);
const setToggle = React.useCallback((nextValue) => setOn(nextValue), []);
return [on, onToggle, setToggle];
};
export default useToggle;

Использование:

Closed

useWindowResizer

const useWindow = () => {
const [data, setData] = React.useState({ width: 0, heigth: 0 });
React.useEffect(() => {
const onResize = (e) => {
setData({
width: e.target.innerWidth,
height: e.target.innerHeigth,
});
};
window.addEventListener("resize", onResize);
return () => {
window.removeEventListener("resize", onResize);
};
}, []);
return data;
};
const useAttachEvent = (event, handler, node = document) => {
React.useEffect(() => {
node.addEventListener(event, handler);
return () => {
node.removeEventListener(event, handler);
};
}, []);
};

Использование

const App = () => {
useAttachEvent("click", () => console.log("Hello"));
// attach event to window
useAttachEvent("click", () => console.log("Hello"), window);
return <div>App</div>;
};

useFetch

const useFetch = (url) => {
const [data, setData] = React.useState(null);
const onFetchData = async () => {
try {
const response = await fetch(url);
const data = await response.json();
setData(data);
} catch (e) {
setData(null);
}
};
React.useEffect(() => {
onFetchData();
}, [url]);
return data;
};

Практика 👩‍💻👨‍💻

  1. Написать хук useTimeout
function Component({ ms }) {
const [isReady, cancel, reset] = useTimeout(ms);
if (isReady) {
return (
<div>
<p>Ready</p>
<button onClick={() => reset()}>Reset</button>
</div>
);
}
return <button onClick={() => cancel()}>Cancel</button>;
}

Дополнительно 😎

  • Реализовать метод reset
  1. Написать хук useInterval
const delay = 1000;
const TimeOut = () => {
const [count, setCount] = React.useState(0);
const [isRunning, start, stop] = useInterval(() => {
setCount(count + 1);
}, delay);
const onClick = isRunning ? stop : start;
return (
<div>
<h1>count: {count}</h1>
<div>
<button onClick={onClick}>{isRunning ? "stop" : "start"}</button>
</div>
</div>
);
};

Почитать 📚

Edit this page on GitHub