Декларативный vs Императивный UI

Декларативный UI описывает что должно быть отображено (React, Vue); императивный — пошагово как изменить DOM (прямые манипуляции через document.getElementById, jQuery).

Зачем нужно

Понимание этого различия объясняет, почему React так устроен и почему прямые DOM-манипуляции в React-коде считаются антипаттерном. Декларативный подход перекладывает управление DOM на фреймворк: разработчик описывает желаемое состояние, React находит минимальный набор изменений (через Virtual DOM). Это снижает когнитивную нагрузку и устраняет целый класс ошибок.

Где используется

  • Декларативный: React, Vue, Svelte, SwiftUI, Flutter, Jetpack Compose
  • Императивный: vanilla JavaScript, jQuery, прямые DOM API, анимационные библиотеки (GSAP)
  • Гибрид: useRef в React для доступа к DOM при необходимости (фокус, canvas, сторонние библиотеки)

Сравнение подходов

Задача: показать/скрыть элемент

// Императивный подход (jQuery/vanilla JS)
// Описываем КАК изменить DOM

let isVisible = false;

document.getElementById('toggle').addEventListener('click', () => {
  isVisible = !isVisible;

  const content = document.getElementById('content');
  if (isVisible) {
    content.style.display = 'block'; // явно управляем DOM
    content.classList.add('visible');
  } else {
    content.style.display = 'none';
    content.classList.remove('visible');
  }

  const button = document.getElementById('toggle');
  button.textContent = isVisible ? 'Скрыть' : 'Показать';
});
// Декларативный подход (React)
// Описываем ЧТО должно быть — React сам управляет DOM

function Toggle() {
  const [isVisible, setIsVisible] = useState(false);

  // UI — функция от состояния: UI = f(state)
  return (
    <div>
      <button onClick={ => setIsVisible(prev => !prev)}>
        {isVisible ? 'Скрыть' : 'Показать'}
      </button>
      {isVisible && <div className="content">Содержимое</div>}
    </div>
  );
}

Задача: список с добавлением элементов

// Императивный
const ul = document.getElementById('list');
const items = ;

function addItem(text) {
  items.push(text);
  const li = document.createElement('li');
  li.textContent = text;
  ul.appendChild(li); // ручное обновление DOM
}

function removeItem(index) {
  items.splice(index, 1);
  ul.querySelectorAll('li')[index].remove(); // опасно — индексы сдвигаются
}
// Декларативный
function ItemList() {
  const [items, setItems] = useState();

  const addItem = (text) => {
    setItems(prev => [...prev, { id: crypto.randomUUID, text }]);
    // React сам обновит DOM минимальным изменением
  };

  const removeItem = (id) => {
    setItems(prev => prev.filter(item => item.id !== id));
  };

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          {item.text()}
          <button onClick={ => removeItem(item.id)}>Удалить</button>
        </li>
      ))}
    </ul>
  );
}

Когда нужен императивный доступ в React

// useRef — легальный «выход» для DOM-манипуляций
function AutoFocusInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    // Фокус на элемент — оправданная императивная операция
    inputRef.current?.focus();
  }, );

  return <input ref={inputRef} />;
}

Частые ошибки

  • Прямое обращение к DOM в Reactdocument.querySelector('.my-button').style.display = 'none' — обходит Virtual DOM и приводит к рассинхронизации.
  • Перемешивание парадигм — попытка управлять состоянием через DOM и через React одновременно.
  • Использование ref вместо state — если изменение данных должно вызывать перерендер, нужен useState, не useRef.

Связанные темы

Ресурсы