Component
1. 컴포넌트란?
React 컴포넌트는 UI를 구성하는 독립적이고 재사용 가능한 코드 조각입니다. 컴포넌트를 사용하면 복잡한 UI를 작은 단위로 나누어 관리할 수 있어, 코드의 재사용성과 유지보수성이 높아집니다. React에서는 두 가지 주요 유형의 컴포넌트가 있습니다.
1.1 컴포넌트의 종류
React 컴포넌트는 클래스형(Class Component)과 함수형(Function Component) 두 가지 방식으로 작성할 수 있습니다.
// 함수형 컴포넌트
function Welcome(props) {
return <h1>안녕하세요, {props.name}님!</h1>;
}
// 클래스형 컴포넌트
class Welcome extends React.Component {
render() {
return <h1>안녕하세요, {this.props.name}님!</h1>;
}
}
초기 React에서는 state 관리와 생명주기 메서드를 사용할 수 있는 클래스형 컴포넌트가 주로 사용되었지만 현재는 함수형 컴포넌트를 주로 사용합니다. React 16.8 버전에서 부터 Hooks가 도입되며 state 관리와 생명주기 기능을 함수형 컴포넌트에서도 사용할 수 있게 되었으며 클래스형 컴포넌트에 비해 함수형 컴포넌트의 코드가 더 간결하고 이해하기 쉽습니다. 또한 함수형 컴포넌트가 메모리 자원을 덜 사용하고 빌드 후 결과물의 크기가 더 작다는 장점이 있습니다.
해당 교안은 함수형 컴포넌트를 기준으로 작성이 되었으며 추후에 클래스형 컴포넌트에 대해 더 자세히 알아보도록 하겠습니다.
2. 컴포넌트 만들기
웹 페이지를 만들다 보면 비슷한 UI가 여러 번 반복되는 경우가 많습니다. 다음과 같은 상품 목록 페이지를 예시로 들어보겠습니다.
function App() {
return (
<div>
<h1>인기 상품</h1>
<div className="product">
<img src="product1.jpg" alt="상품1" />
<h2>캣타워</h2>
<p>가격: 50,000원</p>
<button>장바구니에 담기</button>
</div>
<div className="product">
<img src="product2.jpg" alt="상품2" />
<h2>고양이 간식</h2>
<p>가격: 15,000원</p>
<button>장바구니에 담기</button>
</div>
<div className="product">
<img src="product3.jpg" alt="상품3" />
<h2>장난감 쥐</h2>
<p>가격: 10,000원</p>
<button>장바구니에 담기</button>
</div>
</div>
);
}
위 코드에서는 상품을 표시하는 동일한 구조의 코드가 세 번이나 반복됩니다. 만약 상품 카드의 디자인을 수정하거나 새로운 기능을 추가하려면 모든 카드의 동일한 부분을 하나하나 수정해야 합니다. 현재는 상품 목록이 3개이니 수정을 하는데 시간이 오래 걸리지 않겠지만 상품의 수가 늘어난다면 어떨까요? 이런 경우 컴포넌트를 사용하면 코드를 훨씬 효율적으로 관리할 수 있습니다.
// 상품 카드 컴포넌트
function ProductCard({ image, name, price }) {
return (
<div className="product">
<img src={image} alt={name} />
<h2>{name}</h2>
<p>가격: {price}원</p>
<button>장바구니에 담기</button>
</div>
);
}
// 상품 리스트 데이터
const products = [
{ id: 1, image: 'product1.jpg', name: '캣타워', price: '50,000' },
{ id: 2, image: 'product2.jpg', name: '고양이 간식', price: '15,000' },
{ id: 3, image: 'product3.jpg', name: '장난감 쥐', price: '10,000' },
];
function App() {
return (
<div>
<h1>인기 상품</h1>
{products.map((product) => (
<ProductCard
key={product.id}
image={product.image}
name={product.name}
price={product.price}
/>
))}
</div>
);
}
완성된 코드를 보면 오히려 이전보다 길어졌지만 최상위 App()에서 바라보면 소스코드가 보다 보기 편하게 정리된 것을 볼 수 있습니다.
function App() {
return (
<div>
<h1>인기 상품</h1>
{products.map((product) => (
<ProductCard
key={product.id}
image={product.image}
name={product.name}
price={product.price}
/>
))}
</div>
);
}
이렇게 만든 <ProductCard/>를 컴포넌트라고 부릅니다. 그리고 App()도 하나의 컴포넌트임을 알게 되었습니다. 이때 주의할 점은 컴포넌트의 이름을 지을 때는 첫 글자를 대문자로 작성해야 컴포넌트로 해석됩니다. 이렇게 컴포넌트를 사용하면 동일한 UI를 여러 번 작성할 필요 없이 재사용을 할 수 있습니다. 또 디자인이나 기능을 수정할 때 컴포넌트 코드만 수정하면 되기 때문에 유지보수에도 용이하며, 코드의 구조 파악이 쉬워 가독성도 올라갑니다.
React 함수형 컴포넌트에서 화살표 함수와 일반 함수 선언은 대부분의 경우 동일하게 동작합니다.
// 일반 함수 선언
function MyComponent() {
return <div>Hello</div>;
}
// 화살표 함수
const MyComponent = () => {
return <div>Hello</div>;
};
주요 차이점은 호이스팅입니다. 일반 함수는 선언 전에 호출할 수 있지만, 화살표 함수는 선언 후에만 사용할 수 있습니다. 또한 화살표 함수는 자체 this를 갖지 않지만, 함수형 컴포넌트에서는 this를 거의 사용하지 않으므로 실질적인 차이가 없습니다. 실무에서는 팀의 컨벤션에 따라 선택하며, 두 방식 모두 널리 사용됩니다.
3. Props 파라미터
위에서 만든 <ProductCard/>컴포넌트를 확인해보면 { image, name, price }파라미터를 받고 있는 것을 볼 수 있습니다. 이 파라미터를 리액트에서는 props라고 부릅니다. props는 'properties'의 줄임말로, 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 때 사용하는 속성입니다. 마치 HTML 태그에 속성을 넣어주는 것처럼 React 컴포넌트에도 속성을 넣어줄 수 있습니다.
3.1 Props 사용하기
3.1.1 기본적인 props 사용
props는 컴포넌트의 매개변수로 전달되며, 객체 형태로 받아서 사용합니다. props 객체의 name과 age 프로퍼티에 접근하는 JS 문법이기 때문에 사용할 때는 꼭 중괄호로 묶어줘야 합니다.
function Hello(props) {
return (
<div>
<h1>안녕, 내 이름은 {props.name}!</h1>
<p>나이는 {props.age}살이야.</p>
</div>
);
}
// 사용
function App() {
return <Hello name="지지" age={20} />;
}
3.1.2 구조 분해 할당 사용
props 객체를 구조 분해 할당을 통해 더 간단하게 사용할 수 있습니다.
function Hello({ name, age }) {
return (
<div>
<h1>안녕, 내 이름은 {name}!</h1>
<p>나이는 {age}살이야.</p>
</div>
);
}
3.1.3 여러 props 한번에 전달하기
객체의 속성들을 전개구문(...)를 사용하여 한번에 전달할 수 있습니다.
function App() {
const userInfo = {
name: '지지',
age: 20,
type: '사막여우',
};
return <Hello {...userInfo} />;
}
3.2 Props의 특징
- 읽기 전용
props는 컴포넌트 내부에서 수정할 수 없습니다. 변경이 필요한 경우 부모 컴포넌트에서 새로운 값을 전달해야 합니다.
- 단방향 데이터 흐름
데이터는 부모에서 자식 컴포넌트로만 전달됩니다.
- 기본값 설정
props의 기본값을 설정할 수 있습니다.
function Hello({ name = '라이캣' }) {
return <h1>안녕, {name}!</h1>;
}
3.3 다양한 props 전달하기
props로는 다양한 타입의 데이터를 전달할 수 있습니다. 문자열, 숫자형 등등 심지어 JSX 까지 데이터를 넘길 수 있습니다.
function HelloProps({ name, age, someFunc, someJSX, someArr, someObj }) {
return (
<div>
<p>
이름: {name}, 나이: {age}
</p>
<p>함수 실행: {someFunc()}</p>
<div>JSX 전달: {someJSX}</div>
<p>배열 내용: {someArr.join(', ')}</p>
<p>객체 값: {someObj.value}</p>
</div>
);
}
function App() {
return (
<HelloProps
name="개리"
age={10}
someFunc={() => '안녕하세요!'}
someJSX={<span>전달된 JSX입니다</span>}
someArr={[1, 2, 3]}
someObj={{ value: '테스트' }}
/>
);
}
4. 컴포넌트 파일 분리
React 프로젝트가 커질수록 모든 컴포넌트를 하나의 파일에 작성하면 관리하기 어려워집니다. 컴포넌트를 별도의 파일로 분리하여 관리하면 코드의 재사용성과 유지보수성을 높일 수 있습니다.
4.1 컴포넌트 내보내기/불러오기
컴포넌트 파일을 불러올 때는 export/import 구문을 사용합니다.
// src/Components/Hello.jsx
import React from 'react';
import '../styles/App.css';
function Hello({ name }) {
return <h1>안녕, {name}!</h1>;
}
export default Hello; // 기본 내보내기
// src/Components/Time.js
import React from 'react';
function Time() {
const now = new Date();
return <h2>현재 시각: {now.toLocaleTimeString()}</h2>;
}
export default Time;
// App.js
import Hello from './Components/Hello'; // 컴포넌트 불러오기
import Time from './Components/Time'; // 컴포넌트 불러오기
function App() {
return (
<div>
<Hello name="개리" />
<Time />
</div>
);
}
export default App;
이렇게 컴포넌트를 분리하면 각 컴포넌트의 역할이 명확해지고, 필요한 곳에서 쉽게 재사용할 수 있습니다. 또한 여러 개발자가 동시에 작업할 때도 충돌을 줄일 수 있습니다.
왜 export default로 내보내야 할까요?
export default를 사용하면 모듈에서 하나의 기본 값을 내보낼 수 있습니다. 이는 모듈을 가져올 때 더 간단한 구문을 사용할 수 있게 해줍니다. 예를 들어, export default로 내보낸 컴포넌트는 import 구문에서 중괄호 없이 가져올 수 있습니다.
// 기본 내보내기
export default function Hello() {
return <h1>Hello, world!</h1>;
}
// 가져오기
import Hello from './Hello';
반면에, export를 사용하면 여러 값을 내보낼 수 있지만, 가져올 때는 중괄호를 사용해야 합니다.
// 명명된 내보내기
export function Hello() {
return <h1>Hello, world!</h1>;
}
// 가져오기
import { Hello } from './Hello';
export로만 컴포넌트를 내보낼때는 하나의 모듈에서 여러 값을 내보낼 수 있으며 내보낸 이름과 동일한 이름으로 가져와야합니다. 다른 이름으로 사용하려면 as 키워드로 별칭을 지정해야합니다.
import { add as plus } from './math';
또한 * as 구문을 사용하면 모든 내보내기를 하나의 객체로 가져올 수 있습니다.
import * as MathUtils from './math';
// MathUtils.add(), MathUtils.subtract() 형태로 사용
따라서 export default는 모듈에서 하나의 주요 기능이나 컴포넌트를 내보낼 때 유용하며, 코드의 가독성을 높이고 사용하기 쉽게 만들어줍니다.
5. 실습
5.1 프로필 카드 만들기
사용자의 정보를 표시하는 프로필 카드 컴포넌트를 만들어보세요.
// App.js
function App() {
return (
<div>
<ProfileCard
image="profile1.jpg"
name="지지"
job="프론트엔드 개발자"
isWorking={true}
company="위니브"
/>
</div>
);
}
[요구사항]
- 이미지, 이름, 직업, 회사명을 props로 전달받아 표시합니다
- isWorking이 true일 경우 "재직중", false일 경우 "구직중"으로 표시합니다
- isWorking이 true인 경우에만
${company}에서 일하는 중입니다!텍스트를 띄워주세요. - 스타일링은 자유룝게 적용시켜주세요.
5.2 버튼 컴포넌트 만들기
다양한 스타일과 기능을 가진 재사용 가능한 버튼 컴포넌트를 만들어보세요.
function App() {
return (
<div>
<Button
text="로그인"
color="blue"
size="large"
onClick={() => alert('로그인 버튼 클릭!')}
/>
<Button
text="취소"
color="red"
size="small"
onClick={() => alert('취소 버튼 클릭!')}
/>
<Button text="저장" color="green" disabled={true} />
</div>
);
}
[요구사항]
- text: 버튼에 표시될 텍스트
- color: 버튼의 색상 (blue, red, green)
- size: 버튼의 크기 (small, medium, large)
- onClick: 클릭 시 실행될 함수
- disabled: 버튼의 활성화/비활성화 상태