Хуки
- Хуки - это функции, которые нужно вызывать для того, чтобы они работали;
- Хуки используют деструктуризацию массива, чтобы можно было сразу присвоить переменные равные элементам массива;
- Хуки принято именовать префиксом
use__
; - Хуки должны использоваться в FC только на высшем уровне;
const arr = ['a', 'b', 'c'];
const [const1, const2, const3] = arr;
const1; // 'a'
const2; // 'b'
const3; // 'c'
useState()
- Позволяет использовать локальный стейт внутри функциональных React-компонентов, а не только в классовых как было в ранних версиях React;
- Функции, изменяюшие state принято именовать с помощью префикса
set__
;
const [state, setState] = useState(initialState);
state
- переменная, хранящее состояние. Как правило, это строка, число, булеан или массив любых данных;setState
- функция, изменяющаяstate
;initialState
- первоначальное значениеstate
;
Одиночный state
import { useState } from 'react';
...
const [count, setCount] = useState(0) || useState<number>(0); // with TS;
...
<button onClick={() => setCount(count + 1)}>Change count</button>
Возврат страрого state
prevState
- возвращает предыдущий state.
import { useState } from 'react';
...
const [count, setCount] = useState(0);
const onButtonClick = () => {
setCount(count + 1);
// идентичная запись с prevState
setCount(prevState => prevState + 1);
}
...
<button onClick={onButtonClick}>Change count</button>
setGameState: React.Dispatch<React.SetStateAction<TPandaGameState>>;
...
setGameState((prevState: TPandaGameState) => {
return {
...prevState,
gameStatus: 'take-cards',
};
});
// короткая запись
setGameState((prevState: TPandaGameState) => ({
...prevState,
gameStatus: 'take-cards',
}));
Множественный state
Вместо большого количества одиночных можно использовать объект состояний, на подобие как было в классовых компонентах;
initialState
может принимать даже функцию, которая возвращает значение;
import { useState } from 'react';
// типизация state
type TState = {
value1: number;
value2: boolean;
value3: string[];
};
const initialState: TState = {
value1: 1,
value2: false,
value3: ['a', 'b', 'c'],
}
const [state, setState] = useState<TState>(initialState);
...
const onButtonClick = () => {
setState({
// or need to add all prev state values
...state,
value1: 2,
value2: true,
value3: ['a'],
});
};
...
Типизация setState
Если setState
передается как пропс, то его нужно типизировать
type TObj = {
id: number;
name: string;
}
// родитель
const [state, setState] = useState<number>(0);
const [obj, setObj] = useState<TObj>({ id: 0, name: '' });
// потомок
type TProps = {
// изменение примитива
setState : React.Dispatch<React.SetStateAction<number>>;
// изменение объекта
setObj: React.Dispatch<TObj>;
}
useEffect()
- Если хук
useState
заменилstate
классового компонента, то хукuseEffect
заменил все методы жизненного цикла (componentDidMount
,componentDidUpdate
,componentWillUnmount
и т.д.). - Может использоваться несколько раз в одном компоненте.
- Можно управлять перерендером компонента.
- Не блокирует отрисовку разметки
componentDidMount() (первый рендер, запросы к API)
- Будет срабатывать при первом рендере (при каждом вмонтировании компонента в
Virtual Dom
); - Запросы к API (fetch data);
- Изменение локального state при загрузке (change
initialState
); - Лоадеры;
import { useState, useEffect } from 'react';
const SomeComp = () => {
const [users, setUsers] = useState<any[]>([]);
// ЗАПРОС к API - будет срабатывать при первом рендеринге компонента
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(json => setUsers(json));
}, []);
return (
<ul>
{users?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
componentDidUpdate() (props/state update)
- Будет срабатывать при первом рендере;
- Будет срабатывать каждый раз после изменения зависимостей;
- Зависимостей может быть несколько через запятую;
- Во внутрь
useEffect
можно записывать условия, чтобы была бóльшая гибкость;
import { useState, useEffect } from 'react';
const SomeComp = () => {
const [count, setCount] = useState(0);
// будет срабатывать при первом ренденре
// будет срабатывать каждый раз при изменении count
useEffect(() => {
console.info('counter value: ', count);
}, [count]);
return (
<button onClick={() => setCount(count + 1)}>{count}</button>
);
}
componentDidUpdate() (возврат предыдущего state)
- Не будет срабатывать при первом рендере;
- Будет срабатывать каждый раз ДО изменения зависимостей;
- Поэтому возвращает предылущий state;
import { useState, useEffect } from 'react';
const SomeComp = () => {
const [count, setCount] = useState(0);
useEffect(() => {
// не будет срабатывать при первом рендере!
// вернет предыдущее состояние count до изменения
return () => {
console.info('previos counter value: ', count);
};
}, [count]);
return (
<button onClick={() => setCount(count + 1)}>{count}</button>
);
}
componentWillUnmount()
- Срабатывает при каждом демонтировании компонента из
Virtual Dom
- remove event listeners
- clear cache, storages
- return to
initialState
import { useState, useEffect } from 'react';
const SomeComp = () => {
const [windowWidth, setWindowWidth] = useState<number | null>(null);
// resizeX update state method
const resizeXListener = () => {
const updateWidth = window.innerWidth;
setWindowWidth(updateWidth);
console.log('windowWidth', windowWidth);
};
useEffect(() => {
// add event listener
window.addEventListener('resize', resizeXListener);
// remove event listener
return () => window.removeEventListener('resize', resizeXListener);
}); // do not add [] - will not working
return (
<div>{count}</div>
);
}
Вариации useEffect
В записи useEffect без [ ] вообще по сути нет смысла, так как этот код будет выполняться при каждом рендере, useEffect можно убрать.
const SomeComp = ({ count }: TProps) => {
// сработает при каждом рендере / ререндере
console.log('count inside', count);
// сработает при каждом рендере / ререндере
useEffect(() => {
console.log('count', count);
// если внутри такой записи менять state, то будет бесконечный цикл
setValue(value + 1);
// можно считать кол-во рендеров
});
// сработает 1 раз при загрузке
useEffect(() => {
console.log('count []', count);
}, []);
// сработает 1 раз при загрузке и далее каждый раз при изменении count
useEffect(() => {
console.log('count [count]', count);
}, [count]);
return <div>{count}</div>;
};
useLayoutEffect()
useLayoutEffect
выполняет код до отрисовки в браузере
- Такой же как и
useEffect
, только грузиться ещё до отрисовки html. - Срабатывает раньше чем
useEffect
- Применяется редко
useRef()
- Ссылка на любую другую js-конструкцию
- Ссылка на DOM-элемент
- К DOM-ссылка можно применять все нативные методы работы с DOM
const SomeComp = ({ count }: TProps) => {
const inputRef = useRef<HTMLInputElement>(null);
const [inputValue, setInputValue] = useState<string>('some string');
// можно использовать dom-методы
const onButtonClick = () => {
inputRef.current?.focus();
};
return (
<div>
<input
ref={inputRef}
type='text'
value={inputValue}
onChange={(e: ChangeEvent<HTMLInputElement>) => setInputValue(e.target.value)}
/>
<button onClick={onButtonClick}>Click me</button>
</div>
)
};
useContext()
- Позволяет передавать
state
на любой уровень, что-то вроде встроенногоredux
вreact
- Можно избавиться от
property drilling
- Внутрь контекста можно передавать всё что угодно (примитивы, функции, объекты).
1 шаг - создаем пустой контекст
import { createContext } from 'react';
export type TUserData = {
name: string;
surname: string;
age?: number;
};
export const UserContext = createContext<TUserData>({
name: '',
surname: '',
age: undefined,
});
2 шаг - подключаем контекст к приложению, значение контекста подключаем к useState
import { UserContext } from './UserContext';
const App = () => {
const [personData, setPersonData] = useState<TUserData>({
name: 'Default Username',
surname: 'Default Surname',
age: null,
});
return (
<UserContext.Provider value={personData}>
<button
onClick={() =>
setPersonData({
name: 'Jack',
surname: 'Corbell',
age: 35,
})
}
>
Change UserContext
</button>
<SomeComponent />
</UserContext.Provider>
)
}
3 шаг - используем контекст внутри вложенных компонентов в провайдер
Получаем доступ к personData через хук useContext
. При изменении personData, данные будут обновляться внутри компонента.
Оборачиваем в <Provider />
.
import { useContext } from 'react';
import { UserContext } from './UserContext';
export default function SomeComp() {
const personData = useContext(UserContext);
const { name, surname, age } = personData;
return (
<ul>
<li>{`name: ${name}`}</li>
<li>{`surname: ${surname}`}</li>
<li>{`age: ${age}`}</li>
</ul>
);
}
useMemo()
useMemo - это как useEffect + useState. Чтобы не менять стейт при изменении пропса компонента с useEffect, можно использовать useMemo, в которой в константе сразу будет вычисляться значение// плохо: используем 2 хука для высчитывания значения
const [activePlayer, setActivePlayer] = useState(null);
useEffect(() => {
setActivePlayer(players.find(player.id === activePlayerId));
}, [players])
// хорошо: используем 1 хук useMemo
const activePlayer = useMemo(() => {
return players.find(player.id === activePlayerId);
}, [players]);
// если никаких зависимостей нет, то (будет создаваться только при загрузке и не будет перерендериваться)
const activePlayer = useMemo(() => {
return // do something
}, []);
- Оптимизация от перерендеров, кеширование вычисляемого значения;
- Если есть в компоненте функции, которые вычисляют какие-либо значения, то они будут заново вызываться при любых перерендерах;
- Чтобы такого не было, нужно использовать
useMemo()
; - Синтаксис:
useMemo(() => fn, [deps])
;
import { useMemo } from 'react';
const Test = ({ name, count }: { name: string; count: number }) => {
// returnTitle - функция, которая будет постоянно вызываться при изменении любых props (например count)
const returnTitle = () => (
<span>
Hello, <b>{name}!</b>
</span>
);
// BAD: always calls
const title = returnTitle();
// GOOD: запоминает результат и функция будет вызываться только при изменении name
const title = useMemo(() => returnTitle(), [name]);
return (
<>
<div>{title}</div>
<div>
Count: <b>{count}</b>
</div>
</>
);
};
export default Test;
useCallback()
useCallback - как useMemo только для экшенов. Управление реактивностью// плохо - при каждом перерендере эта функция будет каждый раз пересоздаваться
// Не управляем реактивностью
const onClick = () => {
props.onClick?.apply(null);
!isAuthenticated ? login() : onRename();
};
// хорошо - эта функция будет каждый раз пересоздаваться только при изменении [isAuthenticated, onRename]
// Управляем реактивностью
const onClick = useCallback(() => {
props.onClick?.apply(null);
!isAuthenticated ? login() : onRename();
}, [isAuthenticated, onRename]);
// если никаких зависимостей нет, то (будет создаваться только при загрузке и не будет перерендериваться)
const onClick = useCallback(() => {
// do somethint
}, []);
- Такой же как
useMemo()
только кэшируется не вычисляемое значение; - Если компонент принимает какую-ниб пропс-функцию (например, которая меняет стейт), то при ререндере род. компонента, дочерний компонент также будет перерендеривааться так как ссылка на эту функцию будет обновлена;
- Чтобы такого не было, нужно использовать
useCallback()
; - Синтаксис:
useCallback(fn, deps)
;
export default function ToDoList() {
const [count, setCount] = useState<number>(0);
const onSetCount = useCallback(() => setCount(count + 1), [count]);
return (
<section className={styles.ToDoList}>
{/* count */}
<div>
Count: <b>{count}</b>
<div>
<button onClick={onSetCount1}>Update count</button>
</div>
</div>
</section>
)
}
useReducer()
TODO
useImperativeHandle()
TODO
useDebugValue()
TODO