Post #750
821
❌ Избегайте использования нескольких переменных состояния для отслеживания статуса.
✅ Вместо этого используйте перечисления (enums).
✍️ @React_lib
✅ Вместо этого используйте перечисления (enums).
✍️ @React_lib
- 👍 9
RE @react_lib
Showing posts older than #751 · Back to latest
useMemo и useCallback не спасают автоматическиkey - классика.index, React может пересоздавать элементы там, где не должен.onChange ты триггеришь тяжёлый ререндер (фильтрация, сортировка, построение списков, пересчёт графиков, сложные компоненты).useDeferredValue для “тяжёлого” поискаquery, а на deferredQuery.useMemo для вычисленийfilter/sort/map по большому массиву - мемоизируй результат по зависимостям.memo.deferred)?memo)?useMemo и useCallback - когда они реально помогают, а когда вредятuseMemo/useCallback НЕ нужныmemo - компоненты и не используются как deps.arr.map на 20 элементов).React.memo
const onSelect = React.useCallback((id) => {
setSelected(id);
}, []);
const filtered = React.useMemo(() => heavyFilter(items, query), [items, query]);
const params = React.useMemo(() => ({ q: query, page }), [query, page]);
memo?useEffectuseEffect ты подписываешься на что-то и используешь колбэк/данные, которые постоянно меняются → эффект перезапускается, подписки пересоздаются.ref.
function useLatest(value) {
const ref = React.useRef(value);
React.useEffect(() => { ref.current = value; }, [value]);
return ref;
}
const onMessageLatest = useLatest(onMessage);
React.useEffect(() => {
const unsub = socket.subscribe((msg) => {
onMessageLatest.current(msg);
});
return unsub;
}, []); // подписка не пересоздаётся

<Component data={{ a: 1 }} />
useMemo.
onClick={() => doSomething()}
memo - он всё равно будет ререндериться.useCallback.React.memo без анализа - зло.memo бесполезен и даже мешает.useState в родителе, а нужен только одному дочернему компоненту - привет лишним обновлениям.memo, useMemo, useCallback
useEffect(() => {
fetchData(id);
}, [id, fetchData]);
fetchData — это функция, которая переопределяется при каждом рендере. В итоге эффект срабатывает чаще, чем должен, даже если id не менялся.
const fetchData = useCallback((id: string) => {
// ...
}, []);
const fetchData = (id: string) => {
// ...
};
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect — ключ к стабильному и предсказуемому поведению компонентов.<UserProfile />.<LoginForm />.<Spinner /> (крутилку). Как только данные пришли — показываем <DataList />.isLoggedIn === true, и "Войти", если isLoggedIn === false.<ErrorMessage /> вместо контента.cart.items.length === 0, и список товаров, если в ней что-то есть.? :) - самый частый выбор.{isLoading ? <Spinner /> : <Content />}&&) - идеален, когда вам нужно что-то показать или не показать ничего (нет ветки "else").{hasMessages && <NotificationBadge />}if/else - используется внутри тела компонента, до return, для более сложной логики.useWindowSize. Он будет отслеживать ширину и высоту окна браузера. Это пригодится, если вам нужно менять верстку (например, скрывать сайдбар) при изменении размера экрана.useState для хранения размеров.useEffect для подписки на событие resize.useWindowSize.js.
import { useState, useEffect } from 'react';
// Наш кастомный хук
function useWindowSize() {
// 1. Инициализируем состояние
const [windowSize, setWindowSize] = useState({
width: undefined,
height: undefined,
});
useEffect(() => {
// Функция для обновления состояния
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
// Устанавливаем размер сразу при загрузке
handleResize();
// 2. Добавляем слушатель события изменения размера окна
window.addEventListener("resize", handleResize);
// 3. Очистка: удаляем слушатель при размонтировании
return () => window.removeEventListener("resize", handleResize);
}, []); // Пустой массив зависимостей = запускаем один раз
return windowSize;
}
export default useWindowSize;
import React from 'react';
import useWindowSize from './useWindowSize';
const App = () => {
// Просто вызываем наш хук
const { width, height } = useWindowSize();
return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<h1>📏 Размер окна</h1>
<p>Ширина: <strong>{width}px</strong></p>
<p>Высота: <strong>{height}px</strong></p>
{width < 600 ? (
<p>📱 Похоже, вы на мобильном устройстве!</p>
) : (
<p>💻 Это десктопная версия.</p>
)}
</div>
);
};
export default App;
React.memo + useCallback
const TaskItem = React.memo(function TaskItem({ task, onToggle }) {
console.log("Render:", task.title);
return (
<div>
<input
type="checkbox"
checked={task.done}
onChange={() => onToggle(task.id)}
/>
{task.title}
</div>
);
});
function TaskList() {
const [tasks, setTasks] = useState(data);
const toggle = useCallback(id => {
setTasks(prev =>
prev.map(t => (t.id === id ? { ...t, done: !t.done } : t))
);
}, []);
return tasks.map(task => (
<TaskItem key={task.id} task={task} onToggle={toggle} />
));
}
React.memo не даст компоненту перерендериться без изменения реальных данных.useCallback создаёт стабильную ссылку на функцию.
onSelect, который создаётся при каждом рендере родителя:
const handleSelect = id => {
setSelected(id);
};
useCallback:
const handleSelect = useCallback((id) => {
setSelected(id);
}, []);
Мемоизируйте только то, что реально ререндерится слишком часто.
Слепая мемоизация = грязный код.
useEffect(() => {
fetchData();
}, []);
userId, и кто-то лениво добавляет её в массив зависимостей, не понимая, что произойдёт при каждом изменении.
function useUser(userId) {
const [user, setUser] = useState(null);
useEffect(() => {
let ignore = false;
async function load() {
const data = await fetch(`/api/user/${userId}`).then(r => r.json());
if (!ignore) setUser(data);
}
load();
return () => (ignore = true);
}, [userId]);
return user;
}
user.useEffect.
React.memo(). Это предотвратит ненужные перерисовки, если пропсы не изменились.
const OptimizedComponent = React.memo(({ data }) => {
return <div>{data}</div>;
});
useContext сложных объектов без мемоизации. Используйте useMemo и useCallback для оптимизации.
const value = useMemo(() => ({ user, logout }), [user]);
useState, но лучше разбить его на части. Так обновление одной переменной не будет вызывать ререндер всего компонента.
const [name, setName] = useState("");
const [age, setAge] = useState(0);
React.lazy для загрузки компонентов по требованию.
const LazyComponent = React.lazy(() => import("./BigComponent"));
react-window или react-virtualized.
import { FixedSizeList as List } from "react-window";
<List height={400} itemCount={items.length} itemSize={35}>
{({ index, style }) => <div style={style}>{items[index]}</div>}
</List>;

localStorage, отслеживание размеров окна, дебаунс запросов. И вот тут кастомные хуки реально спасают.localStorage 👇
import { useState } from "react";
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
const setStoredValue = (newValue: T) => {
setValue(newValue);
localStorage.setItem(key, JSON.stringify(newValue));
};
return [value, setStoredValue] as const;
}
export default useLocalStorage;
const [theme, setTheme] = useLocalStorage("theme", "light");
hooks и складывать туда такие полезности.