카테고리 없음

Styled-component

woody-j 2023. 12. 22. 21:47
내가 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>
);