리액트는 컴포넌트 기반의 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 />
</>
);
}
순수성을 위해 프로퍼티로 넘기기
function Web({ person }) {
return <h1>물결 웹 파트원 #{cnt}명</1>;
}
export default function SOPT() {
return (
<>
<Web cnt={2} />
<Web cnt={4}/>
</>
);
}
해당 예시는 매우 간단한 예시이지만, 프로젝트의 볼륨에 따라 컴포넌트에서 예측할 수 없는 결과들이 나타난다면 좋지 않겠죠.
사이드 이펙트와 React
함수형 프로그래밍에서는 순수성(purity)이 매우 중요하다는 것을 위의 내용을 통해 알았을 것이다. 순수 함수는 동일한 입력에 대해 항상 동일한 출력을 내놓으며, 외부 상태에 영향을 미치지 않죠. 하지만 결국엔 어딘가에서 무언가가 바뀌어야 한다. 화면을 업데이트하고, 애니메이션을 시작하고, 데이터를 변경하는 것, 이러한 변화들을 우리는 사이드 이펙트(side effects)라고 부른다.
사이드 이펙트는 컴포넌트의 렌더링 과정에서 발생하는 것이 아니라, 그 "사이드"에서 발생하는 것이다. 따라서 React에서는 이러한 사이드 이펙트를 다룰 때 신중해야 한다. 올바른 위치에서 올바른 방식으로 처리하지 않으면, 코드의 예측 가능성이 떨어지고 유지 보수가 어려워질 가능성이 높아진다.
그렇다면 React에서 사이드 이펙트는 어디에 위치하나요?
React에서는 사이드 이펙트를 이벤트 핸들러(event handler)에 포함시키는 것이 일반적이다. 이벤트 핸들러는 버튼 클릭, 입력 필드 변경 등 특정 사용자 상호작용이 발생했을 때 실행되는 함수이다. 중요한 점은, 이벤트 핸들러가 컴포넌트 내부에 정의되어 있어도 렌더링 중에는 실행되지 않는다는 것이다. 따라서 이벤트 핸들러는 반드시 순수할 필요가 없으며, 사이드 이펙트를 안전하게 처리할 수 있는 좋은 위치라고 할 수 있다.
예를 들어, 사용자가 버튼을 클릭했을 때 데이터를 서버에 저장하거나, 화면에 알림 메시지를 띄우는 작업은 전형적인 사이드 이펙트이며, 이러한 작업은 이벤트 핸들러에 포함될 수 있다.
useEffect의 역할과 한계
때로는 모든 옵션을 다 사용해도 적절한 이벤트 핸들러를 찾을 수 없는 상황이 발생하기도 한다. 이 경우, React에서는 useEffect 훅을 사용하여 사이드 이펙트를 처리할 수 있다. useEffect는 컴포넌트가 렌더링된 이후에 특정 코드를 실행하도록 React에 지시하는 훅으로, 예를 들어 데이터 페칭 같은 작업을 수행할 때 자주 사용된다.
하지만 useEffect를 사용하는 것은 최후의 수단이어야 한다. 무분별한 사용은 코드의 복잡성을 증가시키고, 의도하지 않은 타이밍에 사이드 이펙트가 실행될 수 있기 때문이다. 가능하다면, 렌더링 과정만으로 필요한 로직을 표현하는 것이 React 철학에 더 가깝다고 할 수 있다.
🧐useEffect가 "최후의 수단"으로 간주되는 이유가 뭐야?
1. 부수효과의 혼잡성 증가
- useEffect는 주로 API 호출, 이벤트 리스너 등록, 타이머 설정 등 컴포넌트의 렌더링 외의 작업을 처리하는 데 사용된다. 그러나, useEffect 내에서 상태 변경이 빈번하게 발생하면 렌더링 사이클에 예상치 못한 영향을 줄 수 있다. 이는 컴포넌트의 렌더링과 동작을 예측하기 어렵게 만들 수 있다.
2. 의존성 배열 관리의 어려움
- useEffect의 의존성 배열을 제대로 관리하지 않으면, 의도하지 않은 무한 루프에 빠지거나 의존성 변경 사항이 누락되는 문제가 발생할 수 있다. 의존성 배열은 useEffect가 실행되는 조건을 지정하는데, 이 배열을 잘못 관리하면 성능 저하나 버그로 이어질 수 있습니다. => 실제로 의존성 배열에 무엇을 지정해줄지 계산 or 예측하는 것은 개발자 입장에서도 큰 부담이 된다.
3. 리액트의 선언적 접근 방식과의 불일치
- 리액트는 선언적 UI 라이브러리로, 상태 변화에 따라 UI가 자동으로 업데이트되도록 설계되어 있다. 그러나 useEffect는 선언적인 렌더링 흐름을 깨고 명령형 코드로 변경 작업을 추가하므로, 상태와 UI의 일관성이 흐려질 수 있다.
4. 추적 및 디버깅의 어려움
- useEffect 내에서 상태가 변경되면 그에 따라 여러 효과가 발생할 수 있어, 문제가 발생했을 때 원인을 추적하고 디버깅하기가 어렵다. 또한, 잘못된 순서로 효과가 발생하거나 취소되지 않으면, 비정상적인 동작을 초래할 수 있다.
5. 불필요한 렌더링 유발 가능성
- useEffect를 남발하게 되면 불필요하게 여러 번 렌더링이 발생할 수 있다. 예를 들어, 의존성 배열이 변경될 때마다 useEffect가 실행되기 때문에, 의존성 관리가 잘못되면 컴포넌트가 필요 이상으로 자주 렌더링될 수 있다.
useEffect는 되도록이면 사용하지 않아야한다!
tmi) 리액트 쿼리를 사용하는 이유 중 하나가 useEffect를 사용하지 않는다는 점도 큰 이유 중 하나이다.
React에서의 궁극적인 목표는 컴포넌트가 "상태에 따라 어떻게 화면을 그릴 것인가"에만 집중하도록 하는 것이다. 이벤트 핸들러와 useEffect는 필요할 때 사용하는 도구이지만, 가능하면 렌더링 자체로 로직을 해결해 보자! 상태와 props만으로 컴포넌트를 순수하게 유지하는 연습은 코드의 예측 가능성을 높이고, 디버깅과 유지 보수를 쉽게 만들어준다.
UI 트리로서의 리액트
리액트의 UI는 본질적으로 트리 구조이다. 루트 컴포넌트에서 시작하여 여러 개의 하위 컴포넌트들로 나누어져 있으며, 이러한 구조는 DOM 트리와 밀접하게 연관되어 있다. 리액트는 컴포넌트 트리를 사용하여 뷰 계층을 나누고, 이러한 트리를 통해 애플리케이션의 복잡한 UI를 효율적으로 관리한다.
리액트 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는 본질적으로 컴포넌트 트리로 구성되어 있으며, 이러한 트리를 효과적으로 관리하는 것이 성능 최적화의 핵심이다. 리액트의 트리 구조를 잘 이해하고 각 컴포넌트의 역할을 명확히 함으로써, 더 나은 사용자 경험을 제공할 수 있다.
'나야, 리액트 스터디' 카테고리의 다른 글
[week2] 컴포넌트의 순수성 (3) | 2024.11.03 |
---|---|
[week 2] - 조건부 렌더링, 리스트 렌더링, 컴포넌트를 순수하게 유지하기, 트리로서의 UI (5) | 2024.11.03 |
[week2] 순수 컴포넌트 (5) | 2024.11.03 |
[week2] - 조건부 렌더링, 리스트 렌더링, 컴포넌트를 순수하게 유지하기, 트리로서의 UI (2) | 2024.11.03 |
[week 1] 리액트 컴포넌트와 JSX (4) | 2024.10.27 |