프레임워크가 관리하는 UI 컴포넌트 내부에서 document.querySelector와 같은 네이티브 DOM API를 직접 사용하는 것은 예측 불가능한 버그, 심각한 성능 저하, 그리고 유지보수성 저하를 야기합니다. 대신 프레임워크의 상태 관리와 선언적 렌더링 방식을 활용하여 UI를 제어해야 합니다.
현대 프론트엔드 프레임워크(React, Vue, Angular 등)는 선언적인(Declarative) 방식으로 UI를 구축하고 관리합니다. 개발자는 UI의 '상태(State)'를 정의하고, 프레임워크가 이 상태를 실제 DOM에 반영하는 역할을 담당합니다. 이를 위해 대부분의 프레임워크는 가상 DOM(Virtual DOM)과 효율적인 Reconciliation 알고리즘을 사용하여 실제 DOM 업데이트를 최소화하고 최적화합니다. 하지만 프레임워크가 관리하는 컴포넌트 내부에서 document.querySelector, element.appendChild, element.style.left = '...' 와 같은 네이티브 JavaScript DOM API를 직접 사용하는 것은 이러한 프레임워크의 핵심 원칙을 정면으로 위배합니다.
이는 다음과 같은 심각한 문제를 야기합니다:
offsetTop, offsetLeft 등 레이아웃을 강제하는 속성 접근이나 DOM 트리를 크게 변경하는 작업은 더욱 치명적입니다. 프레임워크는 변경 사항을 일괄 처리하고 최적화하는 반면, 직접 조작은 즉각적인 강제 업데이트를 유발합니다.프레임워크의 강력한 기능을 활용하여 선언적으로 UI를 조작해야 합니다.
useState, useReducer, Redux/MobX와 같은 전역 상태 관리 라이브러리 등을 활용하여 데이터 흐름을 명확하게 하고, UI는 이 상태의 함수로만 표현되도록 합니다. 상태가 변경되면 프레임워크가 자동으로 필요한 DOM 업데이트를 처리합니다.useRef(React)와 같은 프레임워크가 제공하는 Refs 기능을 사용합니다. 하지만 Refs는 필요한 최소한의 범위 내에서만 사용해야 하며, UI 상태를 변경하는 목적으로는 사용하지 않아야 합니다. Refs를 통해 DOM에 접근하더라도, 상태와 분리된 DOM 속성(e.g., scrollLeft)을 읽거나, 외부 라이브러리 함수를 호출하는 등 제한적인 용도로 활용해야 합니다.state로 관리하거나, CSS 모듈, Styled Components와 같은 CSS-in-JS 라이브러리를 사용하여 프레임워크의 렌더링 주기에 통합합니다. 조건부 렌더링이나 목록 렌더링은 프레임워크가 제공하는 JSX/템플릿 문법을 활용하여 상태 변화에 따라 UI가 선언적으로 변경되도록 합니다.import React, { useEffect, useRef } from 'react';
function BadComponent() {
const containerRef = useRef(null);
useEffect(() => {
// ⚠️ 안티패턴: 직접 DOM 조작
if (containerRef.current) {
const button = document.createElement('button');
button.textContent = '클릭하세요!';
button.style.backgroundColor = 'red'; // 직접 스타일 변경
containerRef.current.appendChild(button); // 직접 DOM 요소 추가
// ⚠️ 안티패턴: 컴포넌트 라이프사이클 외에서 이벤트 리스너 추가/제거
button.addEventListener('click', () => {
alert('버튼 클릭!');
// ⚠️ 안티패턴: 컴포넌트 상태가 아닌 DOM을 직접 변경
containerRef.current.style.border = '2px solid blue';
});
}
// 클린업 함수에서 직접 추가한 이벤트 리스너 제거 안함 (메모리 누수 가능성)
}, []);
return (
<div ref={containerRef}>
<h2>직접 DOM을 조작하는 컴포넌트</h2>
{/* 이 div 내부에 직접 DOM이 추가될 예정 */}
</div>
);
}
export default BadComponent;import React, { useState } from 'react';
function GoodComponent() {
const [isClicked, setIsClicked] = useState(false);
const [borderColor, setBorderColor] = useState('none');
const handleClick = () => {
setIsClicked(true);
setBorderColor('2px solid blue'); // 상태 업데이트로 스타일 변경 유도
alert('버튼 클릭!');
};
// 렌더링 로직은 상태에 따라 선언적으로 변경
// isClicked 상태에 따라 버튼의 텍스트와 배경색이 변경됨
// borderColor 상태에 따라 div의 border 스타일이 변경됨
return (
<div style={{ border: borderColor }}>
<h2>선언적으로 UI를 관리하는 컴포넌트</h2>
<button
onClick={handleClick}
style={{ backgroundColor: isClicked ? 'lightgreen' : 'red' }} // 상태에 따른 스타일 변경
>
{isClicked ? '클릭되었습니다!' : '클릭하세요!'}
</button>
</div>
);
}
export default GoodComponent;