Как React отслеживает изменения?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как React отслеживает изменения
React отслеживает изменения используя несколько механизмов, основной из которых — Virtual DOM и сравнение объектов по ссылке (reference equality).
Virtual DOM и Reconciliation
Virtual DOM — это абстрактное представление реального DOM в памяти:
// Компонент выполняется и возвращает Virtual DOM
function Counter() {
const [count, setCount] = React.useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
// Virtual DOM выглядит примерно так:
{
type: 'div',
props: { children: [
{ type: 'p', props: { children: 'Count: 0' } },
{ type: 'button', props: { onClick: [...], children: 'Increment' } }
]}
}
Процесс отслеживания изменений
Шаг 1: Состояние меняется
setCount(1) // Меняем state
Шаг 2: Компонент перевыполняется
// React вызывает функцию компонента снова
// Новый Virtual DOM:
{
type: 'div',
props: { children: [
{ type: 'p', props: { children: 'Count: 1' } },
{ type: 'button', props: { onClick: [...], children: 'Increment' } }
]}
}
Шаг 3: Diffing (сравнение)
Олдый Virtual DOM Новый Virtual DOM
count: 0 =====> count: 1
React сравнивает и находит разницу
Шаг 4: Reconciliation (синхронизация)
React обновляет только изменённые части реального DOM:
// Вместо обновления всего DOM
// React обновляет только <p> элемент
dom.querySelector('p').textContent = 'Count: 1'
Сравнение по ссылке (Reference Equality)
React использует === (строгое равенство) для сравнения:
// Примитивные типы
5 === 5 // true (всегда одно значение)
'hello' === 'hello' // true
// Объекты
{} === {} // false (разные объекты в памяти)
const obj = {}
obj === obj // true (один и тот же объект)
Это критично для React:
function Parent() {
// ❌ Плохо: новый объект создаётся при каждом ререндере
const handleClick = () => console.log('clicked')
return <Child onClick={handleClick} />
}
// При каждом ререндере Parent
// Child видит НОВЫЙ handleClick (другая ссылка)
// и тоже ререндерится, хотя логика не изменилась
function Parent() {
// ✅ Хорошо: функция стабильна
const handleClick = useCallback(() => console.log('clicked'), [])
return <Child onClick={handleClick} />
}
// Теперь handleClick имеет одну и ту же ссылку
// Child не ререндерится без необходимости
Механизм отслеживания State
При изменении state:
function Counter() {
const [count, setCount] = React.useState(0)
// Первый рендер: count = 0
// setCount(1) -> React планирует ререндер
// Второй рендер: count = 1
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
)
}
React отслеживает изменение значения, не вызывает ререндер, если значение не изменилось:
setCount(0) // count уже 0
// React НЕ планирует ререндер (в некоторых режимах)
setCount(1) // count изменился
// React планирует ререндер
Props и Dependencies
React сравнивает props:
function Child({ name }) {
return <div>{name}</div>
}
function Parent() {
const [count, setCount] = useState(0)
// Каждый рендер Parent
// Child получает новый props объект {name: 'John'}
return <Child name="John" />
}
// ВОПРОС: будет ли ререндер Child?
// React сравнивает props:
// {name: 'John'} === {name: 'John'} // false! разные объекты
Это нормально, потому что React сравнивает значения props, а не сами объекты:
const oldProps = { name: 'John' }
const newProps = { name: 'John' }
// React сравнивает:
oldProps.name === newProps.name // true, 'John' === 'John'
// Если Child обёрнут в React.memo:
const MemoChild = React.memo(Child)
// Он ререндерится только если props изменился
useEffect Dependencies
React отслеживает dependencies используя === сравнение:
function Counter() {
const [count, setCount] = useState(0)
const [name, setName] = useState('John')
// Эффект запустится когда count изменится
useEffect(() => {
console.log('Count changed:', count)
}, [count]) // зависимость
// React сравнивает:
// oldCount === newCount
// 0 === 1 // true, выполнить эффект
// Проблема с объектами:
const user = { id: 1, name }
useEffect(() => {
console.log('User changed:', user)
}, [user]) // Проблема!
// При каждом ререндере
// {id: 1, name: 'John'} !== {id: 1, name: 'John'}
// Эффект выполняется каждый раз!
// Решение: useMemo
const user = useMemo(() => ({ id: 1, name }), [name])
useEffect(() => {
console.log('User changed:', user)
}, [user])
}
Fiber Architecture
В React 16+ используется Fiber архитектура:
- Render Phase — React строит новый Virtual DOM
- Commit Phase — React обновляет реальный DOM
Это позволяет:
- Pausе/resume работы
- Приоритизировать обновления
- Обрабатывать ошибки
// React может приостановить рендер
// если нужно обработать срочное событие
// Это улучшает responsiveness приложения
Как помочь React эффективнее отслеживать
1. Используй Key для списков
// ❌ Плохо: React не знает какой элемент какой
users.map((user) => <div>{user.name}</div>)
// ✅ Хорошо: React знает элемент по id
users.map((user) => <div key={user.id}>{user.name}</div>)
2. Стабилизируй объекты и функции
// ❌ Плохо
const handleClick = () => {}
const styles = { color: 'red' }
// ✅ Хорошо
const handleClick = useCallback(() => {}, [])
const styles = useMemo(() => ({ color: 'red' }), [])
3. Разделяй логику на мелкие компоненты
// ❌ Плохо: все в одном
function Page() {
const [count, setCount] = useState(0)
return (
<div>
<Counter count={count} />
<HeavyList items={items} /> // ререндерится при изменении count
</div>
)
}
// ✅ Хорошо: отделяем
function CounterSection() {
const [count, setCount] = useState(0)
return <Counter count={count} />
}
function Page() {
return (
<div>
<CounterSection />
<HeavyList items={items} /> // не ререндерится
</div>
)
}
Итоговое резюме
React отслеживает изменения через:
- Virtual DOM — абстрактное представление UI
- Diffing — сравнение старого и нового Virtual DOM
- Reconciliation — обновление реального DOM только где нужно
- Reference Equality (===) — сравнение значений для state, props, dependencies
Понимание этих механизмов помогает писать производительный код.