트리로서 UI 이해하기
React 앱은 서로 중첩된 많은 컴포넌트로 구성되어 있음
React와 많은 다른 UI 라이브러리는 UI를 트리로 모델링 함
- 렌더 트리
- 모듈 의존성 트리
✔️ 트리로서의 UI
트리 : 요소 사이의 관계 모델
React도 React 앱의 컴포넌트 간의 관계를 관리하고 모델링하기 위해 트리 구조를 사용
✔️ 렌더 트리
렌더 트리
- 관점
- 누가 누구를 화면에 그리는가?
- 대상
- 오직 리액트 컴포넌트만 나옴
- 비유
- 가족 족보
컴포넌트의 주요 특징
- 다른 컴포넌트의 컴포넌트를 구성하는 것
- 컴포넌트 중첩 시, 부모 컴포넌트와 자식 컴포넌트의 개념이 생김
- 각 부모 컴포넌트는 다른 컴포넌트의 자식이 될 수 있음
React 앱을 렌더링할 때, 이 관계를 렌더 트리라고 알려진 트리로 모델링할 수 있음
// App.js
import FancyText from './FancyText';
import InspirationGenerator from './InspirationGenerator';
import Copyright from './Copyright';
export default function App() {
return (
<>
<FancyText title text="Get Inspired App" />
<InspirationGenerator>
<Copyright year={2004} />
</InspirationGenerator>
</>
);
}

React 렌더 트리에서 루트 노드는 앱의 Root 컴포넌트
위의 경우 루트 컴포넌트는 App <-- React가 렌더링하는 첫 번째 컴포넌트
렌더 트리는 React 앱의 단일 렌더링을 나타내지만,
조건부 렌더링을 사용하면 부모 컴포넌트가 전달된 데이터에 따라 다른 자식을 렌더링할 수 있음
// App.js
import FancyText from './FancyText';
import InspirationGenerator from './InspirationGenerator';
import Copyright from './Copyright';
export default function App() {
return (
<>
<FancyText title text="Get Inspired App" />
<InspirationGenerator>
<Copyright year={2004} />
</InspirationGenerator>
</>
);
}

위 예시에서, inspiration.type이 무엇이냐에 따라 <FancyText> 또는 <Color>를 렌더링할 수 있음
렌더 트리는 각 렌더링마다 다르게 만들어질 수 있음
컴포넌트 구조
- 최상위 컴포넌트
- 루트 컴포넌트에 가장 가까운 컴포넌트
- 그 아래의 모든 컴포넌트의 렌더링 성능에 영향을 미침
- 가장 복잡성이 높음
- 리프 컴포넌트
- 트리의 맨 아래에 위치
- 자식 컴포넌트가 없음
- 자주 다시 렌더링 됨
✔️ 모듈 의존성 트리 (의존성 트리)
모듈 의존성 트리
- 관점
- 누가 어떤 파일을 import 하는가?
- 대상
- 컴포넌트 + 일반 JS + CSS + 이미지 등 모든 파일
- 비유
- 도서관 인용 목록 (이 책을 쓰기 위해 참고한 문헌들)
모듈 의존성의 트리의 각 노드는 모듈
각 가지는 해당 모듈의 import 문을 나타냄

의존성 트리에서 루트 노드는 루트 모듈 == 엔트리 포인트 파일
ㄴ 일반적으로 루트 컴포넌트를 포함하는 모듈
✔️ 렌더 트리 - 모듈 의존성 트리
렌더 트리 - 'UI의 족보'
모듈 의존성 트리 - '파일들의 수입/수출 관계도'
렌더 트리와 모듈 의존성 트리의 차이점
| 렌더 트리 | 모듈 의존성 트리 | |
| 노드 (최소 단위) | 컴포넌트 (UI 조각) | 모듈 (파일 그 자체) |
| 포함 범위 | 화면에 그려지는 컴포넌트만 | 컴포넌트, JS 로직, CSS, 이미지 등 전부 |
| 연결 기준 | JSX의 부모-자식 관계 | import 문의 연결 관계 |
| 핵심 질문 | "화면에 누가 누구 안에 있는가?" | "파일을 읽어오기 위해 누가 누구를 불렀는가?" |
의존성 트리는 React 앱을 실행하는 데 필요한 모듈을 결정하는 데 유용
프로덕션용으로 빌드할 때, 일반적으로 클라이언트에 제공할 모든 필요 JavaScript를 번들로 묶는 빌드 단계가 있음
ㄴ 해당 작업을 담당하는 도구 = 번들러
--> 번들러는 의존성 트리를 사용하여 포함해야 할 모듈을 결정
이벤트에 응답하기
React에서는 JSX에 이벤트 핸들러를 추가할 수 있음
이벤트 핸들러 : 사용자 상호작용에 따라 유발되는 사용자 정의 함수
ㄴ ex) 클릭, 마우스 호버, 폼 인풋 포커스 등
✔️ 이벤트 핸들러 추가하기
이벤트 핸들러 추가 방법
- 함수 정의
- 이를 적절한 JSX 태그에 prop 형태로 전달
예제) 사용자가 버튼을 클릭할 경우 메세지를 보여주기
- Button 컴포넌트 내부에 handleClick 함수를 선언
- 해당 함수 내부 로직을 구현
- <button> JSX에 onClick={handleClick}을 추가
ㄴ prop 형태로 전달
// App.js
export default function Button() {
function handleClick() { // 이벤트 핸들러
alert('You clicked me!');
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}
이벤트 핸들러 함수의 특징
- 주로 이벤트가 발생하는 컴포넌트 내부에서 정의
- handle로 시작하고 그 뒤에 이벤트명을 붙인 함수명을 가짐
이벤트 핸들러를 JSX 내에서 인라인으로 정의할 수 있는 또다른 방법
<button onClick={function handleClick() {
alert('You clicked me!');
}}>
<button onClick={() => {
alert('You clicked me!');
}}>
^ 화살표 함수
✔️ 이벤트 핸들러로 전달한 함수들은 호출이 아닌 전달이 되어야 함
| 함수를 전달하기 (올바른 예시) | 함수를 호출하기 (잘못된 예시) |
| <button onClick={handleClick}> | <button onClick={handleClick()}> |
- 첫 번째 예시
- handleClick 함수는 onClick 이벤트 핸들러에 전달됨
- React는 이 내용을 기억하고 사용자가 버튼을 클릭하였을 때만 함수를 호출하도록 함
- 두 번째 예시
- handleClick() 끝의 ()가 렌더링 과정 중 클릭이 없었음에도 불구하고 즉시 함수를 실행하도록 만듦
- JSX {} 내의 자바스크립트가 즉시 실행되기 때문
인라인으로 코드를 작성할 때에도 동일한 내용이 다른 형태로 나타남
| 함수를 전달하기 (올바른 예시) | 함수를 호출하기 (잘못된 예시) |
| <button onClick={() => alert('...')}> | <button onClick={alert('...')}> |
- 첫 번째 예시
- 익명 함수
- 매 렌더링마다 내부 코드를 실행하는 것이 아니라, 함수를 생성하여 추후 이벤트에 의해 호출되게 함
- 두 번째 예시
- 클릭 시 실행되는 것이 아니라, 컴포넌트가 렌더링 된 시점에서 실행됨
✔️ 이벤트 핸들러 내에서 Prop 읽기
이벤트 핸들러는 컴포넌트 내부에서 선언됨
--> 해당 컴포넌트의 prop에 접근 가능
// App.js
function AlertButton({ message, children }) {
return (
<button onClick={() => alert(message)}>
{children}
</button>
);
}
export default function Toolbar() {
return (
<div>
<AlertButton message="Playing!">
Play Movie
</AlertButton>
<AlertButton message="Uploading!">
Upload Image
</AlertButton>
</div>
);
}
^ message prop(속성)으로 넘겨주어 이벤트 핸들러에서 사용 가능
✔️ 이벤트 핸들러를 Prop으로 전달하기
위의 경우와 달리, 부모 컴포넌트로 자식의 이벤트 핸들러를 지정하는 경우
ㄴ 부모 : 이벤트 핸들러 선언, 자식 : 이벤트 핸들러 사용
// App.js
// onClick이라는 속성을 받아 <button> 태그의 onClick 속성에 연결해주고,
// children을 받아 버튼 내부의 텍스트를 설정하는 역할
function Button({ onClick, children }) {
return (
<button onClick={onClick}>
{children}
</button>
);
}
function PlayButton({ movieName }) {
// 이벤트 핸들러 따로 생성
function handlePlayClick() {
alert(`Playing ${movieName}!`);
}
return (
<Button onClick={handlePlayClick}>
Play "{movieName}"
</Button>
);
}
function UploadButton() {
// 이벤트 핸들러 따로 생성하지 않고 익명 함수 사용
return (
<Button onClick={() => alert('Uploading!')}>
Upload Image
</Button>
);
}
export default function Toolbar() {
return (
<div>
<PlayButton movieName="Kiki's Delivery Service" />
<UploadButton />
</div>
);
}
✔️ 이벤트 핸들러 Prop 명명하기
- 빌트인 컴포넌트
- HTML에 원래 있는 태그
- ex) <button> <div> <span> <input>
- onClick, onMouseEnter과 같은 브라우저 이벤트 이름만을 지원
- <div onSmash={...}> ← 사용 불가능
- 커스텀 컴포넌트 (사용자 정의 컴포넌트)
- 이벤트 핸들러 prop의 이름을 원하는 대로 명명 가능
- 관습적으로 on으로 시작하여 대문자 영문으로 이어짐
- <Button onSmash={...}> ← 사용 가능
// App.js
function Button({ onSmash, children }) {
return (
// <button> 태그는 onClick prop을 필요로 함
<button onClick={onSmash}>
{children}
</button>
);
}
export default function App() {
return (
// <Button> 컴포넌트는 원하는 대로 명명 가능
<div>
<Button onSmash={() => alert('Playing!')}>
Play Movie
</Button>
<Button onSmash={() => alert('Uploading!')}>
Upload Image
</Button>
</div>
);
}
✔️ 이벤트 전파
이벤트 핸들러는 해당 컴포넌트가 가진 어떤 자식 컴포넌트의 이벤트를 수신할 수도 있음
= 이벤트가 트리를 따라 'bubble' 되거나 '전파된다'고 표현
ㄴ 이벤트는 발생한 지점에서 시작하여 트리를 따라 위로 전달됨
// App.js
export default function Toolbar() {
return (
<div className="Toolbar" onClick={() => {
alert('You clicked on the toolbar!');
}}>
<button onClick={() => alert('Playing!')}>
Play Movie
</button>
<button onClick={() => alert('Uploading!')}>
Upload Image
</button>
</div>
);
}
^ 둘 중의 어느 버튼을 클릭하든지 해당 버튼의 onClick이 먼저 실행된 후, 부모인 <div>의 onClick이 뒤이어 실행됨
✔️ 전파 멈추기
이벤트 핸들러는 이벤트 오브젝트(객체)를 유일한 매개변수로 받음
event를 의미하는 e로 호출되는 것이 일반적
ㄴ 이 오브젝트를 이벤트의 정보를 읽어들이는 데 사용할 수 있음
이벤트 오브젝트는 전파를 멈출 수 있게 해줌
이벤트가 부모 컴포넌트에 닿지 못하도록 막으려면 e.stopPropagation()를 호출
// App.js
function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}
export default function Toolbar() {
return (
<div className="Toolbar" onClick={() => {
alert('You clicked on the toolbar!');
}}>
<Button onClick={() => alert('Playing!')}>
Play Movie
</Button>
<Button onClick={() => alert('Uploading!')}>
Upload Image
</Button>
</div>
);
}
버튼 클릭 시 진행되는 절차
- React가 <button>에 전달된 onClick 핸들러를 호출
- Button에 정의된 해당 핸들러는 다음을 수행
- e.stopPropagation()을 호출하여 이벤트가 더이상 bubbling 되지 않도록 방지
- Toolbar 컴포넌트가 전달해 준 onClick 함수를 호출
- Toolbar 컴포넌트에서 정의된 위 함수가 버튼의 alert를 표시
- 전파가 중단되었으므로 부모인 <div>의 onClick은 실행되지 않음
✔️ 전파의 대안으로 핸들러를 전달하기
- 전파
- 자동 시스템
- 전파의 대안
- 수동 시스템
- 이벤트가 자동으로 부모에게 전파되게 하는 것을 막고, 부모가 준 함수를 직접 호출
- 코드를 읽기 쉽고, 관리가 편해짐
function Button({ onClick, children }) {
return (
<button onClick={e => {
<!-- 1. 전파 중단 -->
e.stopPropagation();
<!-- 2. 이 사이에 다른 코드를 넣어서 부모 함수 실행 전 추가 동작 가능 -->
<!-- 3. 부모 함수 수동 실행 -->
onClick();
}}>
{children}
</button>
);
}
✔️ 기본 동작 방지하기
일부 브라우저 이벤트는 그와 관련된 기본 브라우저 동작을 가짐
ex) <form>의 제출 이벤트 - 내부 버튼 클릭 시 페이지 전체를 reload
기본 동작이 발생하지 않도록 막기 위해 e.preventDefault()를 이벤트 오브젝트에서 호출 가능
// App.js
export default function Signup() {
return (
<form onSubmit={e => {
e.preventDefault();
alert('Submitting!');
}}>
<input />
<button>Send</button>
</form>
);
}
* e.stopPropagation() - e.preventDefault()
- e.stopPropagation()
- 이벤트 핸들러가 상위 태그에서 실행되지 않도록 멈춤
- e.preventDefault()
- 기본 브라우저 동작을 가진 일부 이벤트가 해당 기본 동작을 실행하지 않도록 방지
State: 컴포넌트의 기억 저장소
컴포넌트는 현재 입력값, 현재 이미지, 장바구니와 같은 것들을 '기억'해야 함
React는 이런 종류의 컴포넌트별 메모리를 state라고 부름
✔️ 일반 변수로 충분하지 않은 경우
// App.js
import { sculptureList } from './data.js';
export default function Gallery() {
let index = 0;
function handleClick() {
index = index + 1;
}
let sculpture = sculptureList[index];
return (
<>
<button onClick={handleClick}>
Next
</button>
<h2>
<i>{sculpture.name} </i>
by {sculpture.artist}
</h2>
<h3>
({index + 1} of {sculptureList.length})
</h3>
<img
src={sculpture.url}
alt={sculpture.alt}
/>
<p>
{sculpture.description}
</p>
</>
);
}
^ Next 버튼을 클릭하면 index가 증가하고 다음 조각상을 표시해야 하지만, 해당 코드는 제대로 동작하지 않음
handleClick 이벤트 핸들러는 지역 변수 index를 업데이트하고 있지만 이러한 변화가 보이지 않는 이유
- 지역 변수는 렌더링 간에 유지되지 않음
- React는 이 컴포넌트는 첫 번째 렌더링 이후에 렌더링을 할 때 모든 것을 초기화
- 지역 변수를 변경해도 렌더링을 일으키지 않음
- React는 새로운 데이터로 컴포넌트를 다시 렌더링해야 한다는 것을 인식하지 못함
컴포넌트를 새로운 데이터로 업데이트하기 위해서 필요한 것
- 렌더링 사이에 데이터를 유지
- React가 새로운 데이터로 컴포넌트를 렌더링하도록 유발
useState 훅이 제공하는 것
- 렌더링 간에 데이터를 유지하기 위한 state 변수
- 변수를 업데이트하고 React가 컴포넌트를 다시 렌더링하도록 유발하는 state setter 함수
✔️ state 변수 추가하기
state 변수를 추가하기 위해서는 import 필요
import { useState } from 'react';
const [index, setIndex] = useState(0);
^ index는 state 변수, setIndex는 setter 함수
여기서 [ 와 ] 문법을 배열 구조 분해라고 하며, 배열로부터 값을 읽을 수 있게 해줌
* useState()
해당 함수를 호출하면 무조건 두 개가 들어있는 배열을 반환해줌
- 첫 번째 칸
- 현재 보관 중인 값 = state
- 두 번째 칸
- 그 값을 수정할 수 있는 리모컨 = setter 함수
// App.js
// useState import
import { useState } from 'react';
import { sculptureList } from './data.js';
export default function Gallery() {
const [index, setIndex] = useState(0); // 0은 index의 초기값
function handleClick() {
setIndex(index + 1);
}
let sculpture = sculptureList[index];
return (
<>
<button onClick={handleClick}>
Next
</button>
<h2>
<i>{sculpture.name} </i>
by {sculpture.artist}
</h2>
<h3>
({index + 1} of {sculptureList.length})
</h3>
<img
src={sculpture.url}
alt={sculpture.alt}
/>
<p>
{sculpture.description}
</p>
</>
);
}
✔️ 첫 번째 훅 만나기
React에서 useState와 같이 use로 시작하는 다른 모든 함수를 훅이라고 함
훅 : React가 렌더링 중일 때만 사용할 수 있는 특별한 함수
ㄴ 이를 통해 다양한 React 기능을 연결할 수 있음
훅(use로 시작하는 함수들)은 컴포넌트의 최상위 수준 또는 커스텀 훅에서만 호출 가능
ㄴ 조건문 내에서 선언할 수 X
✔️ useState 해부하기
useState 호출 == React에 이 컴포넌트가 무언가를 기억하기를 원한다고 말하는 것
const [index, setIndex] = useState(0);
^ React가 index를 기억하기를 원함
함수 작동 방식
- 컴포넌트가 처음 렌더링
- index의 초기값으로 useState를 사용해 0을 전달 --> [0, setIndex]를 반환
- React는 0을 최신 state 값으로 기억
- state를 업데이트
- 사용자가 버튼을 클릭하면 setIndex(index + 1)를 호출
- index가 0이었으므로 setIndex(1)이 됨
- setIndex(1)은 React에 index는 1임을 기억하게 하고 또다른 렌더링을 유발
- 사용자가 버튼을 클릭하면 setIndex(index + 1)를 호출
- 컴포넌트가 두 번째로 렌더링
- React는 여전히 useState(0)를 보지만, index를 1로 설정한 것을 기억
- --> [1, setIndex]를 반환
- 위 과정 반복
* useState 호출 시 이름 규칙
const [something, setSomething]과 같이 지정하는 것이 규칙
✔️ 컴포넌트에 여러 state 변수 지정하기
하나의 컴포넌트에 원하는 만큼 많은 타입의 state 변수를 가질 수 있음
// App.js
import { useState } from 'react';
import { sculptureList } from './data.js';
export default function Gallery() {
const [index, setIndex] = useState(0); // 첫 번째 state 변수 - 숫자 타입
const [showMore, setShowMore] = useState(false); // 두 번째 state 변수 - boolean 타입
function handleNextClick() {
setIndex(index + 1);
}
function handleMoreClick() {
setShowMore(!showMore);
}
let sculpture = sculptureList[index];
return (
<>
<button onClick={handleNextClick}>
Next
</button>
<h2>
<i>{sculpture.name} </i>
by {sculpture.artist}
</h2>
<h3>
({index + 1} of {sculptureList.length})
</h3>
<button onClick={handleMoreClick}>
{showMore ? 'Hide' : 'Show'} details
</button>
{showMore && <p>{sculpture.description}</p>}
<img
src={sculpture.url}
alt={sculpture.alt}
/>
</>
);
}
✔️ state는 격리되고 비공개로 유지
state는 화면에서 컴포넌트 인스턴스에 지역적
== 동일한 컴포넌트를 두 번 렌더링한다면 각 복사본은 완전히 격리된 state를 가짐
ㄴ 그중 하나를 변경해도 다른 하나에 영향을 미치지 X
'React > 공식 문서' 카테고리의 다른 글
| useReducer (0) | 2026.05.21 |
|---|---|
| 상태 구조 설계와 공유 (0) | 2026.05.07 |
| 불변 업데이트 패턴 (0) | 2026.04.28 |
| 렌더링과 State 업데이트 (0) | 2026.04.08 |
| Props와 리스트 렌더링 (0) | 2026.03.25 |