← Назад к вопросам

Какие знаешь триггеры для повторного рендеринга компонента?

1.8 Middle🔥 211 комментариев
#React#Оптимизация и производительность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Триггеры повторного рендеринга в React

Рендеринг в React происходит не просто так. Есть чёткие триггеры, которые заставляют компонент пересчитывать вывод. Понимание этих триггеров критично для оптимизации производительности.

1. Изменение State (Состояния)

Это самый частый триггер:

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      {/* При клике → изменится state → рендер! */}
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

// Триггер: setCount() изменит состояние
// Результат: компонент ре-рендерится

2. Изменение Props (Пропсов)

Когда родитель передаёт новые props, компонент ре-рендерится:

function Parent() {
  const [name, setName] = useState('John');

  return (
    <div>
      <Child name={name} />
      <button onClick={() => setName('Jane')}>Change</button>
    </div>
  );
}

function Child({ name }) {
  // При изменении name → ре-рендер!
  return <p>Hello, {name}</p>;
}

// Триггер: новый props name
// Результат: Child ре-рендерится

3. Контекст (Context)

Изменение контекста вызывает ре-рендер всех подписанных компонентов:

const ThemeContext = createContext();

function App() {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={theme}>
      <Header />
      <Main />
      <Footer />
      <button onClick={() => setTheme('dark')}>Toggle</button>
    </ThemeContext.Provider>
  );
}

function Header() {
  const theme = useContext(ThemeContext);
  // При изменении theme → ре-рендер Header!
  return <h1 style={{ color: theme === 'dark' ? 'white' : 'black' }}>Header</h1>;
}

4. Ре-рендер родителя

Когда родитель ре-рендерится, все дети ре-рендерятся (по умолчанию):

function Parent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Parent count: {count}</p>
      {/* Child ре-рендерится вместе с Parent! */}
      <Child message="Hello" />
      <button onClick={() => setCount(count + 1)}>Parent +</button>
    </div>
  );
}

function Child({ message }) {
  // Ре-рендерится каждый раз, когда Parent ре-рендерится
  // Даже если message не изменился!
  console.log('Child rendered');
  return <p>{message}</p>;
}

5. Reducers (Redux, useReducer)

Диспатч action также вызывает ре-рендер:

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      <p>Count: {state.count}</p>
      {/* dispatch() → ре-рендер */}
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
    </div>
  );
}

function reducer(state, action) {
  if (action.type === 'INCREMENT') {
    return { count: state.count + 1 };
  }
  return state;
}

6. Ключи в списках (Key prop)

Когда изменяется список (добавление/удаление элементов), ключи определяют, какие компоненты ре-рендерятся:

function TodoList() {
  const [todos, setTodos] = useState([]);

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li> {/* key очень важен! */}
      ))}
      <button onClick={() => {
        setTodos([...todos, { id: Date.now(), text: 'New' }]);
      }}>Add</button>
    </ul>
  );
}

7. Свойства в объектах/массивах

Изменение свойств в объектах или массивах:

function Profile() {
  const [user, setUser] = useState({ name: 'John', age: 30 });

  // Это ВСЕ вызывают ре-рендер:
  const updateName = () => {
    setUser({ ...user, name: 'Jane' }); // ре-рендер
  };

  const updateAge = () => {
    setUser({ ...user, age: 31 }); // ре-рендер
  };

  return (
    <div>
      <p>{user.name}, {user.age}</p>
      <button onClick={updateName}>Change name</button>
      <button onClick={updateAge}>Change age</button>
    </div>
  );
}

Оптимизация: Предотвращение лишних рендеров

1. React.memo (для функциональных компонентов)

const Child = React.memo(({ name }) => {
  console.log('Child rendered');
  return <p>Hello, {name}</p>;
});

function Parent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <Child name="John" />
      <button onClick={() => setCount(count + 1)}>+</button>
      {/* Child НЕ ре-рендерится, если props не изменились */}
    </div>
  );
}

2. useMemo (мемоизация значений)

function Parent() {
  const [count, setCount] = useState(0);
  const [filter, setFilter] = useState('');

  // Пересчитывается только если filter изменился
  const filteredItems = useMemo(() => {
    console.log('Computing...');
    return items.filter(item => item.name.includes(filter));
  }, [filter]);

  return (
    <div>
      <p>Count: {count}</p>
      <Child items={filteredItems} />
      <button onClick={() => setCount(count + 1)}>Parent +</button>
    </div>
  );
}

3. useCallback (мемоизация функций)

function Parent() {
  const [count, setCount] = useState(0);

  // Функция создаётся один раз
  const handleClick = useCallback(() => {
    console.log('Clicked');
  }, []); // deps empty

  return (
    <div>
      {/* Child не ре-рендерится, так как handleClick не изменилась */}
      <Child onClick={handleClick} />
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

4. Рефакторинг: Поднять state выше

// ❌ Плохо: Child ре-рендерится каждый раз
function Parent() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <Child message="Static" /> {/* ре-рендерится! */}
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

// ✅ Хорошо: разделить компоненты
function Parent() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <StaticChild />
      <Counter />
    </div>
  );
}

5. Состояние в локальном элементе

// ❌ Плохо: state в родителе → все дети ре-рендерятся
function Parent() {
  const [localState, setLocalState] = useState('');
  return (
    <div>
      <input value={localState} onChange={(e) => setLocalState(e.target.value)} />
      <Child /> {/* ре-рендерится каждый раз! */}
    </div>
  );
}

// ✅ Хорошо: state в отдельном компоненте
function InputComponent() {
  const [localState, setLocalState] = useState('');
  return <input value={localState} onChange={(e) => setLocalState(e.target.value)} />;
}

function Parent() {
  return (
    <div>
      <InputComponent />
      <Child /> {/* не ре-рендерится */}
    </div>
  );
}

Инструменты для отладки

// React DevTools Profiler:
// 1. Откроем React DevTools
// 2. Перейдём на вкладку "Profiler"
// 3. Кликнули Record
// 4. Взаимодействовали с компонентом
// 5. Остановили запись
// Увидим все ре-рендеры с причинами

// Или логируем вручную:
import { useWhyDidYouUpdate } from '@react-hookz/web';

function MyComponent(props) {
  useWhyDidYouUpdate('MyComponent', props);
  return <div>{props.name}</div>;
}

Ключевые выводы

Триггеры ре-рендера:

  1. Изменение state
  2. Изменение props
  3. Изменение контекста
  4. Ре-рендер родителя
  5. Dispatch reducer actions
  6. Изменение элементов в списках

Оптимизация:

  • React.memo для предотвращения ре-рендеров
  • useMemo и useCallback для мемоизации
  • Правильная структура компонентов
  • Избегание создания новых объектов в render
  • Использование правильных ключей в списках