React 컴포넌트는 정해진 구조를 따라 작성된다. 이 구조를 이해하면 어떤 React 프로젝트를 보더라도 코드의 흐름을 쉽게 파악할 수 있다.
1. 컴포넌트 파일의 전체 구조
React 컴포넌트 파일은 크게 5개 영역으로 구성된다. 각 영역은 특정한 역할을 담당하며, 일반적으로 다음 순서로 작성된다.
// ===== 1. Import 영역 =====
import React, { useState, useEffect } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import axios from 'axios';
import SomeComponent from './SomeComponent';
// ===== 2. 스타일 정의 영역 =====
const useStyles = makeStyles((theme) => ({
header: {
backgroundColor: 'blue',
padding: '10px',
color: 'white'
},
title: {
fontSize: '24px',
fontWeight: 'bold'
}
}));
// ===== 3. 상수 및 설정값 영역 =====
const API_URL = 'https://api.example.com';
const MAX_ITEMS = 10;
// ===== 4. 컴포넌트 정의 영역 =====
const Header = (props) => {
// 컴포넌트 로직
return (
<div>헤더 내용</div>
);
};
// ===== 5. Export 영역 =====
export default Header;
1-1. Import 영역의 역할
파일의 가장 상단에는 필요한 모듈과 컴포넌트를 import한다. React 라이브러리, Hook, 외부 라이브러리, 다른 컴포넌트 등을 불러온다.
import React, { useState, useEffect } from 'react'; // React 기본 + Hook
import { makeStyles } from '@material-ui/core/styles'; // 스타일 라이브러리
import axios from 'axios'; // HTTP 통신 라이브러리
import Button from './Button'; // 다른 컴포넌트
1-2. 스타일 정의 영역
Material-UI의 makeStyles 같은 스타일링 도구를 사용할 때 이 영역에 스타일을 정의한다. 컴포넌트 외부에 정의하여 재사용성을 높인다.
const useStyles = makeStyles((theme) => ({
container: {
display: 'flex',
justifyContent: 'center',
padding: theme.spacing(2)
},
button: {
backgroundColor: theme.palette.primary.main,
'&:hover': {
backgroundColor: theme.palette.primary.dark
}
}
}));
1-3. 상수 영역
API URL, 설정값, 고정된 데이터 등 변하지 않는 값들을 정의한다. 대문자와 언더스코어로 작성하는 것이 관례다.
const API_URL = 'https://api.example.com';
const TIMEOUT_DURATION = 5000;
const DEFAULT_PAGE_SIZE = 20;
2. 컴포넌트 함수의 내부 구조
컴포넌트 함수 내부도 일정한 순서를 따라 작성한다. 이 순서를 지키면 코드의 가독성이 높아진다.
const Header = (props) => {
// ===== 1. Hook 선언 영역 =====
const classes = useStyles();
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// ===== 2. useEffect 영역 =====
useEffect(() => {
// 초기 데이터 로드
}, []);
// ===== 3. 함수 정의 영역 =====
const handleClick = () => {
console.log('클릭됨');
};
const fetchData = async () => {
// 데이터 가져오기
};
// ===== 4. return (렌더링) 영역 =====
return (
<div className={classes.container}>
<h1>헤더</h1>
</div>
);
};
2-1. Hook 선언 영역
모든 Hook은 컴포넌트 함수의 최상단에 선언한다. 이는 React의 규칙이며, 조건문이나 반복문 안에서 Hook을 사용하면 안 된다.
const Header = (props) => {
// 스타일 Hook
const classes = useStyles();
// 상태 관리 Hook
const [users, setUsers] = useState([]);
const [isOpen, setIsOpen] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
// 커스텀 Hook
const { isLoggedIn, login, logout } = useAuth();
// 나머지 코드...
};
2-2. useEffect의 역할과 사용법
useEffect는 컴포넌트의 생명주기에 따라 특정 동작을 수행할 때 사용한다. 컴포넌트가 화면에 나타날 때, 특정 값이 변경될 때, 컴포넌트가 사라질 때 등의 시점에 코드를 실행할 수 있다.
const Header = (props) => {
const [data, setData] = useState([]);
// 패턴 1: 컴포넌트 마운트 시 1번만 실행
useEffect(() => {
console.log('컴포넌트가 처음 화면에 나타남');
fetchInitialData();
}, []); // 빈 배열 = 최초 1번만
// 패턴 2: 특정 값 변경 시마다 실행
useEffect(() => {
console.log('data가 변경됨:', data);
saveToLocalStorage(data);
}, [data]); // data가 변경될 때마다
// 패턴 3: cleanup 함수 포함
useEffect(() => {
const timer = setInterval(() => {
console.log('1초마다 실행');
}, 1000);
// 컴포넌트가 사라질 때 실행
return () => {
clearInterval(timer);
console.log('타이머 정리');
};
}, []);
return <div>Header</div>;
};
3. useEffect에서 API 호출하기
useEffect 안에서 axios를 사용한 API 호출은 매우 흔한 패턴이다. 컴포넌트가 화면에 나타날 때 서버에서 데이터를 가져오는 작업을 수행한다.
3-1. 기본적인 axios 사용 패턴
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const Header = (props) => {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// 컴포넌트 로드 시 사용자 데이터 가져오기
useEffect(() => {
setLoading(true);
axios.post('/api/users', {
department: 'IT',
active: true
})
.then(response => {
console.log('응답 데이터:', response.data);
setUsers(response.data);
setLoading(false);
})
.catch(error => {
console.error('에러 발생:', error);
setError(error.message);
setLoading(false);
});
}, []); // 빈 배열 = 최초 1번만 실행
return (
<div>
{loading && <p>로딩 중...</p>}
{error && <p>에러: {error}</p>}
{users.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
};
3-2. async/await 패턴 사용하기
async/await를 사용하면 더 깔끔하고 읽기 쉬운 코드를 작성할 수 있다. 하지만 useEffect의 콜백 함수 자체를 async로 만들 수 없기 때문에, 내부에 async 함수를 정의하고 호출하는 방식을 사용한다.
const Header = (props) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
// async 함수를 내부에 정의
const fetchData = async () => {
setLoading(true);
try {
const response = await axios.post('/api/data', {
category: 'news',
limit: 10
});
console.log('데이터 가져오기 성공:', response.data);
setData(response.data);
} catch (error) {
console.error('데이터 가져오기 실패:', error);
} finally {
setLoading(false);
}
};
// 정의한 함수 호출
fetchData();
}, []);
return <div>{/* 렌더링 내용 */}</div>;
};
3-3. dependency array로 조건부 실행하기
useEffect의 두 번째 인자인 dependency array(의존성 배열)를 사용하면 특정 값이 변경될 때만 API를 호출할 수 있다.
const Header = (props) => {
const [pageData, setPageData] = useState(null);
const [userId, setUserId] = useState(1);
// userId가 변경될 때마다 해당 사용자 데이터 가져오기
useEffect(() => {
const fetchUserData = async () => {
try {
const response = await axios.get(`/api/users/${userId}`);
setPageData(response.data);
} catch (error) {
console.error('사용자 데이터 로드 실패:', error);
}
};
fetchUserData();
}, [userId]); // userId가 변경될 때마다 실행
// URL 경로가 변경될 때마다 페이지 데이터 가져오기
useEffect(() => {
const currentPath = window.location.pathname;
console.log('현재 페이지:', currentPath);
axios.get(`/api/pages${currentPath}`)
.then(response => {
setPageData(response.data);
})
.catch(error => {
console.error('페이지 데이터 로드 실패:', error);
});
}, [window.location.pathname]); // URL 경로 변경 시마다 실행
return <div>{/* 렌더링 내용 */}</div>;
};
3-4. 여러 개의 useEffect 사용하기
하나의 컴포넌트에서 여러 개의 useEffect를 사용할 수 있다. 각각 다른 목적과 다른 dependency를 가질 수 있다.
const Header = (props) => {
const [user, setUser] = useState(null);
const [notifications, setNotifications] = useState([]);
const [theme, setTheme] = useState('light');
// Effect 1: 초기 사용자 정보 로드
useEffect(() => {
axios.get('/api/user/me')
.then(response => setUser(response.data));
}, []); // 최초 1번만
// Effect 2: 사용자가 로그인되면 알림 가져오기
useEffect(() => {
if (user) {
axios.get(`/api/notifications/${user.id}`)
.then(response => setNotifications(response.data));
}
}, [user]); // user가 변경될 때
// Effect 3: 테마 변경 시 서버에 저장
useEffect(() => {
if (user) {
axios.post('/api/user/preferences', { theme });
}
}, [theme]); // theme이 변경될 때
return <div>{/* 렌더링 내용 */}</div>;
};
4. JSX 렌더링 영역
return 문 안에는 JSX 문법을 사용하여 화면에 표시할 내용을 작성한다. JSX는 JavaScript XML의 약자로, HTML과 비슷하지만 JavaScript 코드를 함께 사용할 수 있다.
4-1. JSX의 기본 구조
const Header = (props) => {
const classes = useStyles();
const [count, setCount] = useState(0);
return (
<>
{/* Fragment: 여러 요소를 감쌀 때 사용 */}
<div className={classes.header}>
<h1>제목입니다</h1>
<p>카운트: {count}</p>
<button onClick={() => setCount(count + 1)}>
증가
</button>
</div>
<nav className={classes.nav}>
<a href="/">홈</a>
<a href="/about">소개</a>
</nav>
</>
);
};
4-2. 조건부 렌더링
const Header = (props) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
return (
<div>
{/* 로딩 중일 때 */}
{loading && <p>로딩 중...</p>}
{/* 에러가 있을 때 */}
{error && <p style={{ color: 'red' }}>에러: {error}</p>}
{/* 조건에 따라 다른 내용 표시 */}
{isLoggedIn ? (
<div>
<p>환영합니다!</p>
<button onClick={() => setIsLoggedIn(false)}>로그아웃</button>
</div>
) : (
<div>
<p>로그인이 필요합니다</p>
<button onClick={() => setIsLoggedIn(true)}>로그인</button>
</div>
)}
</div>
);
};
4-3. 배열 데이터 렌더링
const Header = (props) => {
const [items, setItems] = useState([
{ id: 1, name: '아이템1', price: 1000 },
{ id: 2, name: '아이템2', price: 2000 },
{ id: 3, name: '아이템3', price: 3000 }
]);
return (
<div>
<h2>상품 목록</h2>
<ul>
{items.map(item => (
<li key={item.id}>
{item.name} - {item.price}원
</li>
))}
</ul>
</div>
);
};
5. Export와 컴포넌트 사용
컴포넌트를 정의한 후에는 반드시 export 해야 다른 파일에서 사용할 수 있다.
5-1. Export 방식
// 방식 1: default export (일반적)
const Header = (props) => {
return <div>헤더</div>;
};
export default Header;
// 방식 2: 선언과 동시에 export
export default function Header(props) {
return <div>헤더</div>;
}
// 방식 3: named export (여러 개 내보낼 때)
export const Header = (props) => {
return <div>헤더</div>;
};
export const Footer = (props) => {
return <div>푸터</div>;
};
5-2. Import와 사용
// 다른 파일에서 Header 컴포넌트 사용하기
// default export한 경우
import Header from './Header';
// named export한 경우
import { Header, Footer } from './Components';
// 사용 예시
function App() {
return (
<div>
<Header />
<main>본문 내용</main>
<Footer />
</div>
);
}
'공부일기.. > 리액트' 카테고리의 다른 글
| [React] 개념 (1) | 2025.12.23 |
|---|