컴포넌트 스타일링
React에는 자체 스타일링 시스템이 없습니다. 일반적인 CSS를 작성하고, JSX가 엘리먼트에 스타일을 붙이는 두 가지 방법을 제공합니다. 클래스 이름(거의 모든 경우를 처리합니다)과 style prop(스타일시트가 미리 알 수 없는 소수의 값들)입니다. 이 장에서는 둘 다와 그 사이의 패턴을 다룹니다. prop이나 상태로부터 클래스 이름을 구성하여 데이터가 변할 때 엘리먼트가 자동으로 다시 스타일링되도록 하는 방식입니다.
JSX의 클래스 이름
JavaScript에서 class는 예약어이므로 JSX는 이 속성을 className이라고 부릅니다. 값은 평문 문자열이고, React는 이를 DOM의 엘리먼트 class 속성으로 설정합니다. 따라서 반대쪽의 CSS는 항상 그래왔던 방식으로 작성됩니다.
function Avatar() {
return <img className="avatar avatar--large" src={photo} alt="Ada Lovelace" />
}한 문자열에 두 개의 클래스 이름이 공백으로 분리되어 있으며, 이는 HTML과 동일합니다. JSX의 camelCase 규칙은 마크업의 속성 이름에 적용됩니다. 클래스 이름 자체는 사용자의 것이므로 kebab-case, BEM 또는 스타일시트에서 이미 사용 중인 모든 규칙을 그대로 사용할 수 있습니다.
스타일시트 임포트하기
컴포넌트 파일은 맨 위의 간단한 import로 필요한 CSS를 가져옵니다.
import './App.css'
export default function App() {
return <h1 className="title">React Facts</h1>
}import의 왼쪽에는 변수가 없고, 컴포넌트는 파일을 읽지 않습니다. 이것은 빌드 도구에 대한 지시입니다. 이 모듈이 그 CSS에 의존하므로 포함하라는 뜻입니다. Vite는 개발 중에 규칙을 페이지에 주입하고 프로덕션 빌드에서 실제 .css 파일을 내보냅니다. React 프로젝트 설정하기는 이미지를 처리하는 동일한 메커니즘을 설명합니다.
import를 어디에 배치할지는 조직의 문제입니다. Button.jsx 맨 위에 Button.css를 import하면 스타일을 그것을 사용하는 컴포넌트 옆에 둘 수 있습니다. 규칙 자체는 전역입니다. .title 선택자는 페이지의 어디서든 그 클래스를 가진 모든 엘리먼트와 일치하며, 어느 파일이 이를 import했는지는 상관없습니다. BEM 같은 네이밍 규칙은 그 공유 네임스페이스를 관리하기 위해 존재하며, CSS Modules과 CSS-in-JS 라이브러리는 앱이 네이밍 규칙만으로는 감당하기 어려워지면 규칙의 범위를 제한합니다.
버전 노트
React 19는 컴포넌트 내부에서 <link rel="stylesheet">와 <style>을 렌더링하는 지원을 추가했으며, precedence prop으로 React가 이들을 문서 head로 끌어올리고 예측 가능하게 순서를 정할 수 있습니다. 이전 버전들은 react-helmet 같은 라이브러리가 필요했습니다. 위의 빌드 도구 import는 모든 버전에서 동일하게 작동합니다.
데이터에 따라 달라지는 클래스 이름
className은 다른 JSX 표현식처럼 중괄호 안에 있으므로 문자열을 계산할 수 있습니다. 각 주사위가 굴림 사이에 고정될 수 있는 주사위 게임을 생각해봅시다. 주사위가 고정되어 있는지 여부는 상태에 포함되고 prop으로 여기에 도달합니다.
function Die({ value, isHeld }) {
return (
<button className={`die ${isHeld ? 'die--held' : ''}`}>
{value}
</button>
)
}모든 주사위는 die를 얻습니다. 고정된 주사위는 die--held도 얻습니다. 스타일시트가 실제 색상과 테두리를 가지고 있으며, 컴포넌트의 유일한 역할은 어느 클래스가 적용되는지 결정하는 것입니다. isHeld가 바뀌면 React가 class 속성을 업데이트하고 브라우저가 다시 그립니다. 컴포넌트 내부의 아무것도 "held"가 어떻게 보이는지 알 필요가 없으므로 이 파일을 변경하지 않고도 그 외관을 바꿀 수 있습니다.
한 가지 문제점: isHeld가 거짓이면 템플릿 리터럴이 뒤에 공백이 있는 "die "를 생성합니다. 브라우저는 이를 무시하지만 devtools에서는 보기 흉합니다. 배열이 더 깔끔하게 처리하고 하나의 조건을 넘어 확장합니다.
const classes = ['die', isHeld && 'die--held'].filter(Boolean).join(' ')
return <button className={classes}>{value}</button>isHeld && 'die--held'는 조건이 실패할 때 false로 계산되고, filter(Boolean)이 이를 제거하며, join(' ')이 남은 것들 사이에 하나의 공백을 놓습니다.
style prop
style prop은 JavaScript 객체를 받습니다. 속성 이름은 CSS 속성의 camelCase 버전이고, 이중 중괄호는 일반적인 표현식 슬롯에 객체 리터럴이 들어있습니다.
<div style={{ backgroundColor: 'darkslateblue', paddingTop: 12 }}>
Styled inline
</div>background-color는 backgroundColor가 되고 padding-top은 paddingTop이 됩니다. 값은 대개 문자열입니다. 숫자를 그대로 전달하면 React가 길이를 받는 속성에 px를 붙이므로 paddingTop: 12는 padding-top: 12px로 렌더링됩니다. CSS가 단위 없음으로 취급하는 속성들은 원래 숫자를 그대로 둡니다. lineHeight, opacity, flexGrow, zIndex 및 fontWeight는 모두 그대로 통과합니다. 다른 모든 단위는 width: '60%' 또는 margin: '2rem'처럼 문자열로 작성해야 합니다.
대부분의 스타일링은 스타일시트에 속하며, 한 클래스가 수십 개의 선언을 가지고 수십 개의 엘리먼트에서 공유될 수 있습니다. style prop은 값이 런타임에만 알려지고 CSS가 도달할 방법이 없을 때 그 자리를 차지합니다.
function ProgressBar({ percent }) {
return (
<div className="progress">
<div className="progress__fill" style={{ width: `${percent}%` }} />
</div>
)
}채우기의 색상, 높이, 전환 및 모서리 반경은 모두 .progress__fill에 있습니다. 스타일시트가 알 수 없는 유일한 값은 데이터에서 오므로 너비이고, 그 값만 인라인으로 갑니다. API에서 도착하는 URL을 가진 배경 이미지도 동일한 형태입니다.
<div className="hero" style={{ backgroundImage: `url(${photo.url})` }} />크기, 위치 지정 및 오버레이는 .hero에 있고, URL만 prop을 통해 옵니다.
인라인 스타일에도 단단한 한계가 있습니다. 스타일 객체는 하나의 엘리먼트 선언을 설명하므로 호버 상태, 미디어 쿼리 또는 키프레임 애니메이션을 표현할 방법이 없습니다. 나머지 스타일링이 얼마나 동적이든 상관없이 이들은 CSS에 속합니다.
class가 예약어이기 때문에 className을 통해 붙인 이미 작성한 CSS입니다. 파일 맨 위에 스타일시트를 import하고, 규칙을 거기에 넣으며, 컴포넌트가 prop이나 상태로부터 문자열을 구성하여 어느 클래스가 적용되는지 결정하도록 하세요. 스타일시트가 미리 알 수 없는 값(데이터에서 오는 너비 같은)에 대해 style prop을 남겨두세요. 다음: Props, 여기서 컴포넌트가 부모로부터 데이터를 받기 시작합니다.

