리액트는 컴포넌트 기반의 UI 라이브러리로, 각 컴포넌트는 더 큰 UI 구조의 일부로 결합된다. 이 아티클에서는 순수 컴포넌트(Pure Component)와 리액트 애플리케이션의 트리 구조로서의 UI를 이해하는 데 초점을 맞춰 보겠습니다.
순수 컴포넌트는 주어진 props와 state에 따라 항상 동일한 출력(렌더링 결과)을 반환하는 컴포넌트이다. 순수 컴포넌트는 같은 입력값이 주어졌을 때 같은 결과를 제공하기 때문에 불필요한 리렌더링을 방지하여 성능을 최적화할 수 있다.
let person = 0;
function Web() {
person = person + 1;
return <h1>물결 웹 #{cnt}명</h1>;
}
export default function SOPT() {
return (
<>
<Web />
<Web />
</>
);
}
person은 컴포넌트 외부에서 선언된 전역 변수이다. Web컴포넌트가 호출될 때마다 person이 증가하게된다. 즉, 컴포넌트를 호출한 횟수에 따라 결과가 달라지게 된다. => 순수성에 어긋남
function Web({ person }) {
return <h1>물결 웹 파트원 #{cnt}명</1>;
}
export default function SOPT() {
return (
<>
<Web cnt={2} />
<Web cnt={4}/>
</>
);
}
이처럼 외부 상태를 직접 변경하지 않고, props를 통해 필요한 값을 전달받도록 작성하면 React의 순수성과 선언적 렌더링 원칙을 잘 지키게 된다.
함수형 프로그래밍에서는 순수성(purity)이 매우 중요하다는 것을 위의 내용을 통해 알았을 것이다. 순수 함수는 동일한 입력에 대해 항상 동일한 출력을 내놓으며, 외부 상태에 영향을 미치지 않죠. 하지만 결국엔 어딘가에서 무언가가 바뀌어야 한다. 화면을 업데이트하고, 애니메이션을 시작하고, 데이터를 변경하는 것, 이러한 변화들을 우리는 사이드 이펙트(side effects)라고 부른다.
사이드 이펙트는 컴포넌트의 렌더링 과정에서 발생하는 것이 아니라, 그 "사이드"에서 발생하는 것이다. 따라서 React에서는 이러한 사이드 이펙트를 다룰 때 신중해야 한다. 올바른 위치에서 올바른 방식으로 처리하지 않으면, 코드의 예측 가능성이 떨어지고 유지 보수가 어려워질 가능성이 높아진다.
React에서는 사이드 이펙트를 이벤트 핸들러(event handler)에 포함시키는 것이 일반적이다. 이벤트 핸들러는 버튼 클릭, 입력 필드 변경 등 특정 사용자 상호작용이 발생했을 때 실행되는 함수이다. 중요한 점은, 이벤트 핸들러가 컴포넌트 내부에 정의되어 있어도 렌더링 중에는 실행되지 않는다는 것이다. 따라서 이벤트 핸들러는 반드시 순수할 필요가 없으며, 사이드 이펙트를 안전하게 처리할 수 있는 좋은 위치라고 할 수 있다.
예를 들어, 사용자가 버튼을 클릭했을 때 데이터를 서버에 저장하거나, 화면에 알림 메시지를 띄우는 작업은 전형적인 사이드 이펙트이며, 이러한 작업은 이벤트 핸들러에 포함될 수 있다.
때로는 모든 옵션을 다 사용해도 적절한 이벤트 핸들러를 찾을 수 없는 상황이 발생하기도 한다. 이 경우, React에서는 useEffect 훅을 사용하여 사이드 이펙트를 처리할 수 있다. useEffect는 컴포넌트가 렌더링된 이후에 특정 코드를 실행하도록 React에 지시하는 훅으로, 예를 들어 데이터 페칭 같은 작업을 수행할 때 자주 사용된다.
하지만 useEffect를 사용하는 것은 최후의 수단이어야 한다. 무분별한 사용은 코드의 복잡성을 증가시키고, 의도하지 않은 타이밍에 사이드 이펙트가 실행될 수 있기 때문이다. 가능하다면, 렌더링 과정만으로 필요한 로직을 표현하는 것이 React 철학에 더 가깝다고 할 수 있다.
React에서의 궁극적인 목표는 컴포넌트가 "상태에 따라 어떻게 화면을 그릴 것인가"에만 집중하도록 하는 것이다. 이벤트 핸들러와 useEffect는 필요할 때 사용하는 도구이지만, 가능하면 렌더링 자체로 로직을 해결해 보자! 상태와 props만으로 컴포넌트를 순수하게 유지하는 연습은 코드의 예측 가능성을 높이고, 디버깅과 유지 보수를 쉽게 만들어준다.
리액트의 UI는 본질적으로 트리 구조이다. 루트 컴포넌트에서 시작하여 여러 개의 하위 컴포넌트들로 나누어져 있으며, 이러한 구조는 DOM 트리와 밀접하게 연관되어 있다. 리액트는 컴포넌트 트리를 사용하여 뷰 계층을 나누고, 이러한 트리를 통해 애플리케이션의 복잡한 UI를 효율적으로 관리한다.
function App() {
return (
<div>
<Header />
<MainContent />
<Footer />
</div>
);
}
function Header() {
return <header>헤더 영역</header>;
}
function MainContent() {
return (
<main>
<Article />
<Sidebar />
</main>
);
}
function Footer() {
return <footer>푸터 영역</footer>;
}
위 예제에서 App 컴포넌트는 루트 노드이며, Header, MainContent, Footer가 자식 노드로 구성된 트리 구조이다. 트리 구조는 컴포넌트 간의 관계를 명확히 보여주며, 부모-자식 간의 데이터 흐름을 이해하는 데 도움을 준다. 이로써 상태(state)나 props가 어떻게 전달되고, 특정 상태 변경이 어떤 영향을 미치는지 쉽게 파악할 수 있다.
리액트에서 UI는 본질적으로 컴포넌트 트리로 구성되어 있으며, 이러한 트리를 효과적으로 관리하는 것이 성능 최적화의 핵심이다. 리액트의 트리 구조를 잘 이해하고 각 컴포넌트의 역할을 명확히 함으로써, 더 나은 사용자 경험을 제공할 수 있다.