Основные факторы, вызывающие повторный рендер компонента в React:
setState или setter из useStateuseMemo или useEffectПример ререндера при изменении состояния:
function Counter() {
const [count, setCount] = useState(0);
// При нажатии кнопки изменяется состояние,
// что вызывает повторный рендер
return (
<div>
<p>Счетчик: {count}</p>
<button onClick={() => setCount(count + 1)}>
Увеличить
</button>
</div>
);
}React компоненты перерисовываются при изменении данных, от которых они зависят. Это фундаментальный принцип реактивного программирования в React, но понимание конкретных причин повторных рендеров критично для создания оптимизированных приложений. 🔍
Любое изменение внутреннего состояния компонента вызывает его перерисовку:
function ToggleButton() {
const [isOn, setIsOn] = useState(false);
console.log("Компонент рендерится"); // Выполняется при каждом изменении состояния
return (
<button onClick={() => setIsOn(!isOn)}>
{isOn ? 'Выключить' : 'Включить'}
</button>
);
}При каждом нажатии кнопки isOn меняется, что запускает новый рендер.
Когда компонент получает новые пропсы от родителя, он перерисовывается:
// Дочерний компонент
function Message({ text }) {
console.log("Message рендерится");
return <p>{text}</p>;
}
// Родительский компонент
function Chat() {
const [message, setMessage] = useState("");
return (
<div>
<input
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<Message text={message} /> {/* Перерендер при каждом изменении ввода */}
</div>
);
}Когда родительский компонент перерисовывается, все дочерние компоненты по умолчанию тоже перерисовываются:
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Увеличить счетчик: {count}
</button>
<Child /> {/* Перерендеривается при каждом клике, хотя пропсы не меняются */}
</div>
);
}
function Child() {
console.log("Child рендерится");
return <div>Дочерний компонент</div>;
}Компоненты, использующие React Context, перерисовываются при изменении значения контекста:
const ThemeContext = React.createContext('light');
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Переключить тему
</button>
{children}
</ThemeContext.Provider>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
console.log("ThemedButton рендерится");
return <button className={theme}>Кнопка с темой</button>;
}При изменении темы все компоненты, использующие ThemeContext, перерисуются.
Изменение зависимостей в хуках может вызвать повторные вычисления и рендеры:
function SearchResults({ query }) {
// Повторно вычисляется при изменении query
const filteredData = useMemo(() => {
console.log("Фильтрация выполняется");
return expensiveFilter(query);
}, [query]);
return (
<ul>
{filteredData.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}Предотвращает перерисовку, если пропсы не изменились:
// Не будет перерисовываться, если name не изменился
const Greeting = React.memo(function Greeting({ name }) {
console.log("Greeting рендерится");
return <h1>Привет, {name}!</h1>;
});Кэширует результаты вычислений между рендерами:
function ProductList({ products, filter }) {
// Фильтрация выполняется только при изменении products или filter
const filteredProducts = useMemo(() => {
return products.filter(product => product.category === filter);
}, [products, filter]);
return (
<ul>
{filteredProducts.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}Предотвращает создание новых функций при каждом рендере:
function Parent() {
const [count, setCount] = useState(0);
// Функция не пересоздаётся при изменении count
const handleClick = useCallback(() => {
console.log('Клик');
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Счетчик: {count}
</button>
<ExpensiveChild onClick={handleClick} />
</div>
);
}
const ExpensiveChild = React.memo(function({ onClick }) {
console.log("ExpensiveChild рендерится");
return <button onClick={onClick}>Дочерняя кнопка</button>;
});// ❌ Плохо: новый объект стилей при каждом рендере
function Button() {
return (
<button
style={{ color: 'blue', fontSize: '14px' }} // Новый объект при каждом рендере
>
Нажми меня
</button>
);
}
// ✅ Хорошо: константа за пределами компонента
const buttonStyle = { color: 'blue', fontSize: '14px' };
function Button() {
return <button style={buttonStyle}>Нажми меня</button>;
}// ❌ Плохо: новая функция при каждом рендере
function Parent() {
return (
<Child onClick={() => console.log('Клик')} /> // Новая функция при каждом рендере
);
}
// ✅ Хорошо: использование useCallback
function Parent() {
const handleClick = useCallback(() => {
console.log('Клик');
}, []);
return <Child onClick={handleClick} />;
}// ❌ Плохо: неуказана зависимость data
function DataList({ data }) {
useEffect(() => {
console.log("Данные изменились:", data);
}, []); // Зависимость data отсутствует
// ... остальной код
}
// ✅ Хорошо: правильно указаны зависимости
function DataList({ data }) {
useEffect(() => {
console.log("Данные изменились:", data);
}, [data]);
// ... остальной код
}console.log в тело компонентаfunction MyComponent(props) {
console.log("MyComponent рендерится", { props });
// ... код компонента
}✅ Основные причины ререндеров в React:
✅ Как оптимизировать перерисовки:
React.memo — предотвращает рендер при неизменных пропсахuseMemo — кэширует результаты вычисленийuseCallback — предотвращает пересоздание функцийПонимание причин повторных рендеров и умение их оптимизировать — ключевой навык для разработки производительных React-приложений. Оптимизируйте осознанно, исходя из реальных проблем производительности, а не преждевременно. 🚀
Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice, добавляйте сайт в закладки и совершенствуйтесь каждый день 💪