Styled-component
내가 Styled-component를 효과적으로 사용하고 있는지 의문이 들어 공식문서를 읽어보기로 했다.
1. Styled-component
React 컴포넌트 시스템을 스타일링하기 위한 css를 어떻게 향상시킬 수 있을지에 대한 고민의 결과물
자동으로 필수적인 CSS:
styled-components는 페이지에 렌더링된 컴포넌트를 추적하고 해당 컴포넌트의 스타일만을 자동으로 삽입합니다.
코드 분할과 결합되면 사용자는 필요한 만큼의 코드만을 로드하게 됩니다.
const MyComponent = styled.div`
/* 스타일 정의 */
`;
클래스 이름 버그 없음:
styled-components는 스타일에 대한 고유한 클래스 이름을 생성합니다.
중복, 겹침 또는 철자 오류에 대해 걱정할 필요가 없습니다.
간편한 CSS 삭제:
코드베이스에서 클래스 이름이 어디에서 사용되는지 알기 어려울 수 있습니다.
styled-components는 스타일이 특정 컴포넌트에 연결되어 있기 때문에 명확합니다.
컴포넌트가 사용되지 않으면 (도구가 감지할 수 있음) 삭제되고, 해당 스타일도 함께 삭제됩니다.
간단한 동적 스타일링:
컴포넌트의 프로퍼티나 전역 테마에 따라 스타일을 쉽게 조정할 수 있습니다.
수작업으로 수십 개의 클래스를 수동으로 관리할 필요가 없습니다.
const MyComponent = styled.div`
color: ${(props) => (props.isActive ? 'blue' : 'black')};
/* 다른 동적 스타일 정의 */
`;
간편한 유지 관리:
컴포넌트에 영향을 주는 스타일을 찾기 위해 서로 다른 파일을 찾아야 할 필요가 없습니다.
어떤 규모의 코드베이스라도 유지보수가 간편합니다.
자동 공급업체 접두사 지정:
현재 표준에 맞게 CSS를 작성하고 styled-components가 나머지를 처리하도록하세요.
1) Props에 따라 적응하기
styled-components를 사용하면 컴포넌트의 스타일을 props에 따라 동적으로 조정
const Button = styled.button<{ $primary?: boolean; }>`
/* Adapt the colors based on primary prop */
background: ${props => props.$primary ? "#BF4F74" : "white"};
color: ${props => props.$primary ? "white" : "#BF4F74"};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid #BF4F74;
border-radius: 3px;
`;
render(
<div>
<Button>Normal</Button>
<Button $primary>Primary</Button>
</div>
);
2) 확장하기
(1) 기존 컴포넌트의 스타일을 상속하여 새로운 컴포넌트로
자주 컴포넌트를 사용하되 특정 경우에는 약간 수정하고 싶을 수 있습니다.
styled() 생성자를 사용하여 새로운 스타일을 추가한 버전을 만들 수 있습니다.
import styled from 'styled-components';
// 기존 버튼 컴포넌트
const Button = styled.button`
/* 일반 버튼 스타일 */
`;
// 새로운 컴포넌트, 기존 버튼 스타일을 상속하고 추가 스타일을 적용
const SpecialButton = styled(Button)`
/* 추가적인 스타일 */
`;
(2) 다형성
새로운 컴포넌트를 만들 때, 기존 컴포넌트의 스타일을 유지하면서 렌더링되는 HTML 태그나 컴포넌트를 "as" 다형성(prop)을 사용 하여동적으로 변경하는 방법
import styled from 'styled-components';
// 일반 버튼 스타일
const Button = styled.button`
/* 일반 버튼 스타일 */
`;
// 토마토 버튼, 기존 버튼 스타일을 상속하고 추가 스타일을 적용
const TomatoButton = styled(Button)`
/* 추가적인 스타일 */
`;
// "as" 다형성(prop)을 사용하여 렌더링되는 요소 동적 변경
const NavigationLink = styled(Button).attrs({ as: 'a' })`
/* 네비게이션 링크 스타일 */
`;
3) styled 메서드를 사용하여 어떠한 컴포넌트에도 스타일을 적용할 수 있는 방법
(1) styled 메서드의 범용성
styled 메서드는 여러분이 만든 자체 컴포넌트나 제3자(서드파티) 컴포넌트에 모두 적용할 수 있습니다.
이 메서드를 사용하면 해당 컴포넌트에 스타일을 동적으로 적용할 수 있습니다.
(2) 태그 이름 직접 전달
styled() 팩토리 호출에 태그 이름을 직접 전달할 수도 있습니다.
예를 들어, styled("div")와 같이 호출하여 직접 HTML 태그를 스타일링할 수 있습니다.
import styled from 'styled-components';
// 여러분이 만든 컴포넌트에 스타일 적용
const MyCustomComponent = styled(MyCustomComponent)`
/* 여러분이 원하는 스타일 */
`;
// 서드파티 컴포넌트에 스타일 적용
const ThirdPartyComponent = styled(ThirdPartyComponent)`
/* 스타일 적용 */
`;
// 직접 HTML 태그에 스타일 적용
const StyledDiv = styled("div")`
/* div 태그에 대한 스타일 */
`;
// styled.tagname을 사용한 예시
const StyledParagraph = styled.p`
/* p 태그에 대한 스타일 */
`;
4) Passed props
(1) 간단한 HTML 요소인 경우
styled-components가 간단한 HTML 요소(예: styled.div)에 스타일을 적용하는 경우,
해당 요소에 알려진 HTML 속성들이 DOM으로 전달됩니다.
이는 일반적으로 사용되는 HTML 속성들이 해당 요소에 그대로 전달되어 예상대로 렌더링될 수 있게 해줍니다.
(2) 사용자 정의 React 컴포넌트인 경우
만약 styled-components가 사용자 정의 React 컴포넌트(예: styled(MyComponent))에 스타일을 적용하는 경우,
해당 컴포넌트에 전달된 모든 프롭스가 그대로 DOM으로 전달됩니다.
이는 React 엘리먼트처럼 사용자 정의 컴포넌트에 전달된 프롭스가 그대로 DOM 요소에 전달되어 동작할 수 있게 해줍니다.
// Create an Input component that'll render an <input> tag with some styles
const Input = styled.input<{ $inputColor?: string; }>`
padding: 0.5em;
margin: 0.5em;
color: ${props => props.$inputColor || "#BF4F74"};
background: papayawhip;
border: none;
border-radius: 3px;
`;
// Render a styled text input with the standard input color, and one with a custom input color
render(
<div>
<Input defaultValue="@probablyup" type="text" />
<Input defaultValue="@geelen" type="text" $inputColor="rebeccapurple" />
</div>
);
5) 고급 선택자 패턴과 중첩
선택자 및 중첩 설명:
1) &:
단일 앰퍼샌드(&)는 해당 컴포넌트의 모든 인스턴스를 가리킵니다.
일반 스타일을 적용할 때 사용됩니다.
2) &:hover:
:hover 의 경우 해당 컴포넌트가 호버되었을 때의 스타일을 정의합니다.
3) & ~ &:
~ 을 사용하여 형제로 있는 다른 해당 컴포넌트에 대한 스타일을 정의합니다. 직접 옆에 있지 않아도 됩니다.
4) & + &:
+ 를 사용하여 다른 해당 컴포넌트 바로 다음에 있는 경우에 대한 스타일을 정의합니다.
5) &.something:
.something 클래스가 추가된 경우 해당 컴포넌트에 대한 스타일을 정의합니다.
6) .something-else &:
.something-else 요소 내부에 있는 해당 컴포넌트에 대한 스타일을 정의합니다.
tabIndex: 0은 HTML 요소의 탭 순서를 지정하는 속성 중 하나입니다.
이 값은 해당 요소를 탭 키를 사용하여 포커스를 이동할 때의 순서를 나타냅니다.
const Thing = styled.div.attrs(() => ({ tabIndex: 0 }))`
color: blue;
&:hover {
color: red; // 해당 컴포넌트가 호버되었을 때
}
& ~ & {
background: tomato; // 해당 컴포넌트의 형제로, 직접 옆에 있지 않아도 됨
}
& + & {
background: lime; // 해당 컴포넌트 다음에 있는 경우
}
&.something {
background: orange; // 추가적인 CSS 클래스 ".something"이 지정된 경우
}
.something-else & {
border: 1px solid; // 다른 요소 내에 있는 경우
}
`;
render(
<React.Fragment>
<Thing>Hello world!</Thing>
<Thing>How ya doing?</Thing>
<Thing className="something">The sun is shining...</Thing>
<div>Pretty nice day today.</div>
<Thing>Don't you think?</Thing>
<div className="something-else">
<Thing>Splendid.</Thing>
</div>
</React.Fragment>
);
6) 조건부 스타일링
1) Input 및 Label 스타일링 :
- Input은 type 속성이 "checkbox"인 <input> 요소로 스타일이 적용됩니다.
- Label은 flexbox를 사용하여 내부의 요소들을 가로로 정렬하고 간격을 주며, 하단에 마진을 설정합니다.
const Input = styled.input.attrs({ type: "checkbox" })``;
const Label = styled.label`
align-items: center;
display: flex;
gap: 8px;
margin-bottom: 8px;
`;
2) LabelText 스타일링 :
- LabelText는 라벨 내부의 텍스트 스타일을 정의합니다.
- 조건부로 모드에 따른 배경색과 텍스트 색을 설정하고, Input이 체크된 상태에서 특정 조건을 충족할 때 특정 스타일을 추가로 적용합니다.
const LabelText = styled.span`
${(props) => {
switch (props.$mode) {
case "dark":
return css`
background-color: black;
color: white;
${Input}:checked + && {
color: blue;
}
`;
default:
return css`
background-color: white;
color: black;
${Input}:checked + && {
color: red;
}
`;
}
}}
`;
3) Render 부분 :
render(
<React.Fragment>
<Label>
<Input defaultChecked />
<LabelText>Foo</LabelText>
</Label>
<Label>
<Input />
<LabelText $mode="dark">Foo</LabelText>
</Label>
<Label>
<Input defaultChecked />
<LabelText>Foo</LabelText>
</Label>
<Label>
<Input defaultChecked />
<LabelText $mode="dark">Foo</LabelText>
</Label>
</React.Fragment>
);
7) 추가 속성 부여
1) 추가 속성(props) 부여하기:
- .attrs 생성자를 사용하여 렌더링된 컴포넌트 또는 엘리먼트로 일부 props를 전달하는 불필요한 래퍼(wrapper)를 피할 수 있습니다.
- .attrs를 사용하면 정적(props가 변하지 않는) 속성을 엘리먼트에 추가하거나 React Router의 Link 컴포넌트에 activeClassName과 같은 서드파티 prop을 전달할 수 있습니다.
- .attrs 객체는 함수도 받을 수 있으며, 이 함수는 컴포넌트가 받는 props를 인자로 받습니다. 반환된 값은 최종 props에 병합됩니다
- 아래의 예제에서는 Input 컴포넌트에 동적 및 정적 속성을 부여하고 있습니다.
const Input = styled.input.attrs<{ $size?: string }>((props) => ({
// 정적(props가 변하지 않는) props 정의
type: "text",
// 동적(props에 따라 변하는) props 정의
$size: props.$size || "1em",
}))`
color: #BF4F74;
font-size: 1em;
border: 2px solid #BF4F74;
border-radius: 3px;
/* 동적으로 계산된 prop을 사용하는 예시 */
margin: ${props => props.$size};
padding: ${props => props.$size};
`;
render(
<div>
<Input placeholder="A small text input" />
<br />
<Input placeholder="A bigger text input" $size="2em" />
</div>
);
2) .attrs 재정의하기:
- attrs를 사용할 때 주의해야 할 점은 스타일드 컴포넌트를 래핑할 때 .attrs가 내부 스타일드 컴포넌트에서 외부 스타일드 컴포넌트로 전파된다는 것입니다.
- 이로 인해 각각의 래퍼에서 .attrs가 중첩되어 적용되며, 이는 CSS 스타일시트에서 이전에 선언된 속성을 나중에 선언된 속성이 덮어쓰는 것과 유사한 동작을 합니다.
- 아래의 예제에서는 Input 컴포넌트의 .attrs가 먼저 적용되고, 그 후에 PasswordInput 컴포넌트의 .attrs가 적용됩니다.
const Input = styled.input.attrs<{ $size?: string }>((props) => ({
type: 'text',
$size: props.$size || '1em',
}))`
border: 2px solid #bf4f74;
margin: ${(props) => props.$size};
padding: ${(props) => props.$size};
`;
// Input의 .attrs가 먼저 적용되고, 그 후에 이 .attrs 객체가 적용됨
const PasswordInput = styled(Input).attrs({
type: "password",
// border는 Input의 border를 덮어씀
border: "2px solid aqua",
})`
border: 2px solid aqua; // Input의 border를 덮어씀
`;
render(
<div>
<Input placeholder="A bigger text input" $size="2em" />
<br />
{/* Input의 size 속성을 여전히 사용할 수 있음 */}
<PasswordInput placeholder="A bigger password input" $size="2em" />
</div>
);