서론
컴포넌트는 React로 UI를 구축하는 핵심 개념이다. 컴포넌트는 UI를 구성하는 블록으로, React 애플리케이션의 모든 요소는 컴포넌트로 나뉜다. 이번 글에서는 React 컴포넌트의 개념과 특징, 그리고 이를 효과적으로 사용하는 방법에 대해 살펴본다.
컴포넌트란?
컴포넌트는 HTML, CSS, JavaScript 코드를 결합하여 재사용 가능한 UI 요소를 만드는 방법이다. 버튼의 rendering 로직과 markup을 한 컴포넌트 내에서 함께 관리함으로써, 이 둘이 항상 동기화 상태를 유지할 수 있게 한다. React에서는 JavaScript가 HTML을 관리하기에 rendering 로직과 markup이 같은 위치, 즉 컴포넌트 내에 존재한다.
React 컴포넌트는 기본적으로 JavaScript 함수이며, 이 함수는 JSX를 반환해 사용자에게 보이는 UI를 구성한다. 컴포넌트는 이름이 대문자로 시작해야 하며, 같은 파일 내에서 여러 개의 컴포넌트를 정의하고 사용할 수 있다. 또한 한 파일에는 하나의 default export만 존재할 수 있지만, 여러 개의 named export를 할 수 있다.
컴포넌트를 어떻게 export하느냐에 따라 import하는 방식이 달라진다. default export를 named export 방식으로 import하려고 하면 오류가 발생한다. 예를 들어:
- default export: export default function Button() {} → import Button from './Button.js';
- named export: export function Button() {} → import { Button } from './Button.js';
컴포넌트 선언과 사용
컴포넌트를 선언할 때는 항상 파일의 최상위 레벨에 정의해야 한다. 컴포넌트를 다른 컴포넌트 내부에 정의하면 성능 저하와 버그를 유발할 수 있기 때문이다. 다음은 잘못된 예시와 올바른 예시이다:
// 🔴 잘못된 방법
export default function Gallery() {
// 컴포넌트를 내부에 정의하지 말아야 한다!
function Profile() {
// ...
}
// ...
}
// ✅ 올바른 방법
export default function Gallery() {
// ...
}
// ✅ 컴포넌트는 최상위 레벨에 선언해야 한다
function Profile() {
// ...
}
이와 같이, 각 컴포넌트는 최상위 레벨에 정의하고 필요한 데이터는 props를 통해 전달하는 것이 중요하다. <MyButton />과 같이 컴포넌트 이름은 대문자로 시작해야 하며, 컴포넌트는 여러 JSX 태그를 반환할 수 없다. 여러 태그를 반환하려면 태그로 감싸야 하는데, 감쌀 태그가 없다면 fragment를 이용하면 된다. fragment는 import하지 않아도 사용할 수 있으며, 렌더링 되지 않아 여러 태그를 감쌀 때 편하게 사용할 수 있다. fragment에 prop을 전달할 때만 fragment를 명시적으로 import하면 된다.
또한 JSX는 JavaScript 안에 markup을 넣을 수 있게 해준다. JSX 속성에서 JavaScript로 탈출하려면 따옴표 대신 중괄호를 사용해야 한다. onClick={handleClick}처럼 함수 호출 뒤에 괄호를 붙이지 말아야 한다. 이벤트 핸들러 함수를 호출하는 것이 아니라, 그 함수 자체를 전달해야 한다.
use로 시작하는 함수들은 훅(Hook)이라고 하며, 기존 훅들을 조합해 자신만의 훅을 작성할 수도 있다. 훅은 다른 함수보다 더 제한적이다. 컴포넌트나 다른 훅의 최상단에서만 호출할 수 있다.
props와 상태 관리
컴포넌트 간 데이터 전달은 props를 통해 이루어진다. props는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 매개체이며, 이 데이터는 불변(immutable)이다. 즉, 컴포넌트는 props를 직접 수정할 수 없고, 변경이 필요할 때는 부모 컴포넌트에서 새로운 props를 전달받아야 한다.
상태(state)는 컴포넌트 내부에서 관리되는 데이터로, 컴포넌트의 동작과 UI를 동적으로 변화시킬 수 있다. 상태를 변경하려면 setState 함수를 사용하며, 이를 통해 React는 해당 컴포넌트를 다시 rendering한다.
일부 컴포넌트는 모든 props를 자식에게 전달하는데, 아래 예제 코드를 보면 알 수 있다. 이때 자식 컴포넌트가 props를 직접 사용하지 않는 경우, spread 문법을 사용하는 것이 편리할 수 있다
function Profile(props) {
return (
<div className="card">
<Avatar {...props} />
</div>
);
}
Clousres
JavaScript는 closure를 지원하는데, 이는 내부 함수(예: handleClick)가 외부 함수(예: Board)에 정의된 변수와 함수에 접근할 수 있다는 것을 의미한다. handleClick 함수는 squares 상태를 읽고 setSquares 메서드를 호출할 수 있는데, 둘 다 Board 함수 내에 정의되어 있기 때문이다.
import { useState } from 'react';
function Board() {
// Board 컴포넌트 내에 squares와 setSquares 상태를 정의한다.
const [squares, setSquares] = useState(Array(9).fill(null));
function handleClick(index) {
// handleClick 함수는 Board 함수 내부에 정의된 함수로,
// squares와 setSquares에 접근할 수 있다.
// closure때문이다.
}
컴포넌트의 순수함과 부수효과(Side effect)
React는 컴포넌트가 순수 함수이기를 기대한다. 순수 함수란 동일한 입력에 대해 항상 동일한 출력을 반환하는 함수로, 부수효과가 없어야 한다. 하지만, UI의 동작을 위해 부수효과가 필요한 경우가 있다. 이때는 useEffect 훅을 사용해 컴포넌트가 rendering된 이후에 부수효과를 실행할 수 있다.
순수함을 유지하면 React 애플리케이션은 성능 향상과 코드의 예측 가능성을 보장받을 수 있다. 예를 들어, 동일한 입력에 대해 동일한 출력이 보장되므로 React는 컴포넌트를 재사용하거나 rendering을 건너뛸 수 있다. React는 개발 모드에서 각 컴포넌트의 함수를 두 번 호출하는 "Strict Mode"를 제공한다. Strict Mode는 순수함을 가지지 않는 컴포넌트를 찾는 데 도움을 준다.
기본적으로 부모 컴포넌트의 상태가 변경되면 모든 자식 컴포넌트가 자동으로 다시 rendering된다. 이는 변경되지 않은 자식 컴포넌트까지 포함한다. re-rendering은 사용자가 눈치채지 못할 수도 있지만, 성능상의 이유로 명확하게 영향을 받지 않은 부분의 re-rendering을 건너뛰고 싶을 때가 있을 수 있다. 불변성은 컴포넌트의 데이터가 변경되었는지 여부를 비교하는 데 매우 유용하다.
컴포넌트 키와 re-rendering
리스트를 rendering할 때 각 항목에 고유한 key를 부여해야 한다. 이 key는 React가 컴포넌트의 식별자로 사용되며, 상태를 유지한 채로 효율적인 re-rendering을 가능하게 한다. key가 없거나 잘못된 경우, React는 컴포넌트를 제대로 관리하지 못해 불필요한 rendering이나 상태 손실이 발생할 수 있다.
리스트가 다시 rendering될 때, React는 각 리스트 항목의 key를 이전 리스트 항목과 비교한다. 현재 리스트에 이전에 없던 key가 있으면 React는 새 컴포넌트를 생성한다. 이전 리스트에 있던 key가 현재 리스트에 없다면, React는 이전 컴포넌트를 제거한다. 두 key가 일치하면 해당 컴포넌트가 이동된다. key는 React에게 각 컴포넌트의 정체성을 알려주며, 이를 통해 React는 re-rendering 간 상태를 유지할 수 있다. 만약 컴포넌트의 key가 변경되면, 그 컴포넌트는 삭제되고 재생성된다.
'React' 카테고리의 다른 글
React와 JSX (0) | 2024.09.05 |
---|---|
React에서 조건부 렌더링 (0) | 2024.09.02 |
React로 UI 구성: 단계별 접근 가이드 (0) | 2024.08.23 |
React lifecycle 이해하기 (0) | 2024.07.10 |
SPA와 MPA의 차이, 작동 원리에 대해서 (feat CSR, SSR) (0) | 2024.07.04 |