컴포넌트 개념
컴포넌트란?
컴포넌트(component)는 UI를 구성하는 독립적이며 재사용 가능한 작은 단위를 의미합니다.
컴포넌트는 UI와 로직을 재사용할 수 있는 단위임. 보통 웹을 추상화하여(기능 단위로 나누어) 페이지를 여러 섹션으로 나눌 때, 하나의 섹션이 하나의 컴포넌트가 됨.
하지만 렌더링이 자주 일어나거나 성능 최적화가 필요하면 더 작게 컴포넌트를 나눌 수 있음. 리액트에서 디핑할 때 컴포넌트 단위가 작으면 재조정하는 부분이 작아져서 렌더링 비용이 절감됨.
렌더링이 트리거된 컴포넌트(useState() 호출 등)가 디핑 과정의 루트가 되고, 디핑은 해당 루트로 부터 시작해 하위 컴포넌트까지 쭉 이어짐.
그런데 만약 A 컴포넌트가 B, C 컴포넌트를 가진 경우에, A가 렌더링될 때 B와 C는 새로운 Props를 받음.
이때 B가 React.memo 등으로 최적화되어 있고, B에게 전달된 Props가 이전과 동일하다면, React는 B의 서브트리 디핑을 건너뜀.
C의 경우, Props가 변경되었거나 최적화가 되어 있지 않다면 디핑을 계속 진행함.
A 컴포넌트가 원래 B, C 컴포넌트를 가졌는데 D, E 컴포넌트로 대체된 경우는 이전 트리를 모두 해체하고 A 컴포넌트 부터 다시 DOM 트리를 구축함.
리액트 디핑 과정
- 루트부터 하위 컴포넌트까지 디핑 시작.
- 일반 컴포넌트: Props 동일해도 디핑 계속 진행.
- 최적화 컴포넌트 (React.memo): Props가 동일하면 서브트리 디핑 건너뜀 (성능 최적화 지점).
- 컴포넌트 대체 시: 이전 트리 해체 후 DOM 트리 재구축 (비용 높음).
컴포넌트를 너무 작게 쪼개면 유지보수가 힘들어질 수 있으므로 주의해야함.
컴포넌트 구성의 장점(요약)
- 재사용 가능
- 유지보수 용이
- 로직 분리 가능(UI, UX)
- 복잡한 상태 관리 가능, 렌더링 비용 절감
리액트 컴포넌트의 종류(클래스형, 함수형)
리액트의 컴포넌트는 클래스 컴포넌트와 함수형 컴포넌트 두 가지가 있음.
클래스 컴포넌트는 리액트의 Component 클래스를 상속받고, render() 메서드를 호출해서 사용함.
이 때 반드시 render() 메서드를 사용해야하며, render() 메서드는 JSX를 리턴함.
import { Component } from 'react';
class App extends Component {
render() {
return <h1>Hello, Class Component!</h1>
}
}
export default App;
함수형 컴포넌트는 함수를 생성한 후 return 문 안에 JSX를 넣어 사용함. 즉 순수 함수처럼 동작하며, Props를 인수로 받아 React 엘리먼트를 리턴함.
export default function App() {
return (
<h1>Hello, Function Component!</h1>
);
}
함수형 컴포넌트는 함수 선언문 방식(export default function App() {...}) 뿐만 아니라, 함수 표현식(const App = function App() {...})이나 화살표 함수(const App = () => {...})를 이용해 정의할 수도 있음.
클래스 컴포넌트와 함수형 컴포넌트 모두 클래스명이나 함수명을 파스칼 케이스로 작성해야함. 왜냐하면 JSX를 리액트 엘리먼트로 변환할 때 파스칼 케이스가 아닌 경우는 html의 기본 태그로 생각하고 리액트가 클래스나 함수를 반환하지 않아 렌더링이 되지 않기 때문. JSX를 사용하는 경우 컴포넌트 명으로 파스칼 케이스를 사용하는 것은 컨벤션 문제가 아니라 문법적 필수 요소임.
예전에는 클래스 컴포넌트가 기본으로 사용되었으나, 지금은 함수형 컴포넌트를 기본으로 사용하고 있음. 왜냐하면 클래스 컴포넌트는 상대적으로 문법이 복잡하고, 이벤트 연결할 때 this를 사용해야하는 등 불편함이 많았음. 리액트 훅이 도입되어 상태 관리를 위해 클래스 컴포넌트를 꼭 사용하지 않아도 되었을 시점부터 리액트 공식에서도 함수형 컴포넌트를 사용하는 것을 권장함.
클래스 컴포넌트
Component클래스 상속render()필수 호출this사용 등 문법 복잡
함수형 컴포넌트
- 순수 JS 함수 사용
return문 안에 JSX- 리액트 훅 도입 후 상태 관리 가능, 공식 권장 방식
컴포넌트 확장자
리액트에서 컴포넌트를 사용할 수 있는 파일 확장자는 .js, .jsx, .ts, .tsx가 있고, 이중 .ts 확장자를 제외하면 모두 JSX 문법을 사용할 수 있음. 컴포넌트는 같은 파일에서 선언하여 사용할 수 있음.
function Header() {
return ({
<h1>hello world!</h1>
});
}
export default App() {
return({
<>
<Header />
<h2>App section</h2>
</>
});
}
하지만 같은 파일 안에서 컴포넌트를 나누는 것은 유지보수 측면에서 좋지 않으므로, 보통 컴포넌트 마다 파일을 나눔.
컴포넌트는 src/components 폴더를 생성하여 위치시키는 것이 관례임.
함수형 컴포넌트를 정의할 때 export default 키워드를 사용하면 다른 컴포넌트에서 컴포넌트를 import할 때 중괄호 없이 바로 불러올 수 있어서 편리함.
export default는 한 파일에서 하나의 기본 컴포넌트만 내보낼 수 있음.
컴포넌트 트리
즉, 하나의 컴포넌트 안에 또 다른 컴포넌트를 포함하는 방식으로 계층적 구조가 형성됩니다. 이렇게 컴포넌트들이 연결된 구조를 컴포넌트 트리(component tree)라고 합니다.
App 컴포넌트는 리액트 애플리케이션에서 가장 바깥쪽에 위치하는 컴포넌트입니다. 그래서 App을 루트 컴포넌트(root component)라고 합니다.
루트 컴포넌트는 리액트에서 가장 바깥에 위치하는 컴포넌트로, App 이름으로 주로 사용함. 루트 컴포넌트로부터 컴포넌트의 계층관계를 표현한 트리를 컴포넌트 트리라고 함. 컴포넌트가 추가되면 루트 컴포넌트나 다른 컴포넌트의 아래에 위치하게 됨.
데이터는 부모인 루트 컴포넌트로 부터 자식인 다른 컴포넌트까지 단방향으로 흐름. 부모는 props를 이용하여 자식에게 값을 전달할 수 있지만, 자식은 부모의 값을 직접 변경할 수 없음.
컴포넌트는 부모-자식 계층 구조를 가지며, 자식 컴포넌트는 특정 부모 안에서만 렌더링 됨. 리액트의 컴포넌트에는 형제관계라는 개념이 명시적으로 존재하지 않음.
리액트 컴포넌트의 주요 특징(요약)
- 부모-자식 관계의 계층 구조
- 부모 -> 자식의 단방향 데이터 흐름
- 재사용성(컴포넌트 반복 사용 가능)
- 상태관리(컴포넌트 자신 만의 상태 가짐)
- 렌더링 최적화