Как избежать ререндер компонента?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Оптимизация: как избежать лишних перерендеров
Ненужные перерендеры замедляют приложение и расходуют ресурсы. React предоставляет несколько инструментов для их предотвращения.
1. React.memo для функциональных компонентов
React.memo оборачивает компонент и предотвращает его перерендер, если пропсы не изменились:
const Child = React.memo(function Child({ name }) {
console.log('Child отрендерился');
return <div>Привет, {name}!</div>;
});
function Parent() {
const [count, setCount] = useState(0);
const [name] = useState('Иван');
return (
<>
<p>Счётчик: {count}</p>
<button onClick={() => setCount(count + 1)}>Увеличить</button>
{/* Child не перерендерится, т.к. name не изменился */}
<Child name={name} />
</>
);
}
Без React.memo компонент Child перерендерился бы каждый раз при клике (когда меняется count).
2. useMemo для дорогих вычислений
useMemo запоминает результат вычисления и пересчитывает только при изменении зависимостей:
function ExpensiveComponent({ items }) {
// Вычисление выполняется один раз при первом рендере
// и повторяется только если items изменится
const sortedItems = useMemo(() => {
console.log('Сортировка...');
return items.sort((a, b) => a - b);
}, [items]);
return <div>{sortedItems.join(', ')}</div>;
}
Важно: используй useMemo только для действительно дорогих вычислений!
3. useCallback для стабильных функций
useCallback запоминает функцию, чтобы она не пересоздавалась каждый раз:
function Parent() {
const [count, setCount] = useState(0);
// Без useCallback эта функция создавалась бы заново при каждом рендере
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []); // пустой массив зависимостей — функция никогда не меняется
return <Child onAction={handleClick} />;
}
const Child = React.memo(({ onAction }) => {
// Без useCallback в родителе Child перерендерился бы каждый раз
return <button onClick={onAction}>Клик</button>;
});
4. shouldComponentUpdate в классовых компонентах
Этот метод вручную контролирует, нужен ли перерендер:
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// Перерендер только если id изменился
return this.props.id !== nextProps.id;
}
render() {
return <div>ID: {this.props.id}</div>;
}
}
5. PureComponent для неглубокого сравнения
Автоматически сравнивает пропсы и состояние:
class MyComponent extends React.PureComponent {
// shouldComponentUpdate реализован с поверхностным сравнением
render() {
return <div>{this.props.name}</div>;
}
}
Внимание: PureComponent сравнивает по ссылкам, не глубоко!
6. Правильная структура состояния
Делись состояние так, чтобы изменения затрагивали только нужные компоненты:
// ❌ Плохо — один большой объект состояния
const [state, setState] = useState({
user: { name: 'Иван' },
products: [...],
filters: {...}
});
// ✅ Хорошо — разделённое состояние
const [user, setUser] = useState({ name: 'Иван' });
const [products, setProducts] = useState([...]);
const [filters, setFilters] = useState({...});
7. Key в списках
Правильный key предотвращает ненужные перерендеры элементов списка:
// ❌ Плохо — index как key
list.map((item, index) => <Item key={index} data={item} />)
// ✅ Хорошо — уникальный id
list.map(item => <Item key={item.id} data={item} />)
8. Кэширование селекторов (Redux/Zustand)
Используй мемоизированные селекторы в Redux:
import { createSelector } from 'reselect';
// Селектор кэшируется и не пересчитывается, если состояние не изменилось
const selectUserName = createSelector(
state => state.user,
user => user.name
);
9. Ленивая загрузка компонентов
Импортируй тяжёлые компоненты только когда они нужны:
const HeavyComponent = lazy(() => import('./Heavy'));
function App() {
return (
<Suspense fallback={<div>Загрузка...</div>}>
<HeavyComponent />
</Suspense>
);
}
Практический пример оптимизации
// ДО — много перерендеров
function App() {
const [users, setUsers] = useState([]);
const [searchText, setSearchText] = useState('');
const filteredUsers = users.filter(u => u.name.includes(searchText));
return (
<>
<input value={searchText} onChange={e => setSearchText(e.target.value)} />
<UserList users={filteredUsers} />
</>
);
}
// ПОСЛЕ — оптимизировано
function App() {
const [users, setUsers] = useState([]);
const [searchText, setSearchText] = useState('');
const filteredUsers = useMemo(
() => users.filter(u => u.name.includes(searchText)),
[users, searchText]
);
return (
<>
<input value={searchText} onChange={e => setSearchText(e.target.value)} />
<UserList users={filteredUsers} />
</>
);
}
const UserList = React.memo(({ users }) => {
return <div>{users.map(u => <p key={u.id}>{u.name}</p>)}</div>;
});
Инструменты для диагностики
// React DevTools Profiler
// 1. Открой React DevTools → Profiler
// 2. Запиши сессию
// 3. Посмотри какие компоненты перерендерились
// Или используй console.log
function MyComponent() {
console.log('Компонент отрендерился');
return <div>...</div>;
}
Правила оптимизации
✓ Сначала измери проблему (DevTools Profiler)
✓ Используй React.memo для дорогих компонентов
✓ Применяй useMemo только если есть реальные замеры
✓ Выдели состояние на нужный уровень
✓ Используй правильные key в списках
✗ Не оптимизируй заранее (premature optimization)
✗ Не оборачивай всё в React.memo
✗ Не используй useMemo везде
✗ Не игнорируй зависимости в хуках
Оптимизация перерендеров — важный навык для создания быстрых React приложений.