HTML&CSS&Javascript/CSS

[CSS-in-JS] Styled Component의 개념 및 사용법

킹우현 2023. 2. 14. 22:47

1) CSS in JS 복습

CSS in JS는 스타일 정의를 CSS 파일이 아닌 JavaScript로 작성된 컴포넌트에 바로 삽입하는 스타일 기법입니다.

 

기존에 웹사이트를 개발할 때는 HTML과 CSS, JavaScript는 각자 별도의 파일에 두는 것이 best practice로 여겨졌었습니다.

하지만 React나 Vue, Angular와 같은 모던 자바스크립트 라이브러리가 인기를 끌면서 웹개발의 패러다임이 바뀌고 있습니다.

 

최근에는 웹 애플리케이션을 여러 개의 재활용이 가능한 빌딩 블록으로 분리하여 개발하는 '컴포넌트 기반 개발 방법'이 주류가 되고 있습니다.

 

따라서, 웹페이지를 HTML, CSS, JavaScript 3개로 분리하는 것이 아니라, 여러 개의 컴포넌트로 분리하고, 각 컴포넌트에 HTML, CSS, JavaScript를 모두 종합하는 패턴이 많이 사용되고 있습니다. (React는 JSX를 사용해서 이미 JavaScript가 HTML을 포함하고 있는 형태를 취하고 있는데, 여기에 CSS-in-JS 라이브러리만 사용하면 CSS도 손쉽게 JavaScript에 삽입할 수 있습니다.)

 

Styled Components는 이렇게 트랜드가 되고 있는 CSS-in-JS 라이브러리 중에서도 가장 널리 사용되고 있는 라이브러리입니다.

2) Styled Component란?

Javascript 파일 내에서 CSS를 사용할 수 있게 해주는 대표적인 CSS-in-JS 라이브러리로 React 프레임워크를 주요 대상으로 한 라이브러리

 

본질적으로, styled-components 라이브러리를 사용하여 리액트 컴포넌트를 쉽게 만들 수 있으며 Javascript 코드 내에서 일반 CSS로 구성 요소의 스타일을 지정함으로써 컴포넌트 간 내부 응집도를 높일 수 있습니다.

 

styled components는 javascript에서 css를 사용 할 수 있도록 도와주는 스타일링 라이브러리입니다. React Component에 특정 스타일링을 할 수 있기 때문에 재사용성을 더 높일 수 있고, javascript에 영향을 받는 스타일링을 간편하게 구현 할 수 있습니다.

 

기존 CSS 스타일링 방식

const divStyle = {
    backgroundColor: "black",
    width: "100px",
    height: "100px",
};

return <div style={divStyle}></div>

styled-component를 사용한 방식

const StyledDiv = styled.div`
    background-color: black;
    width: 100px;
    height: 100px;
`;

return <StyledDiv></StyledDiv>

기존의 React Component 스타일링은 style 속성에 객체를 전달하거나 className을 설정하고 별도의 css 파일을 import하는 방식으로 사용해왔습니다.

 

styled-componentsstyle이 적용된 Component를 직접 생성하기 때문에, 스타일링을 위한 코드 사용량이 줄어드는 효과가 있습니다. 또 key value의 형태가 아닌 css의 문법을 그대로 사용하기 때문에 기존 css의 사용법보다 가독성도 높습니다.

3) Styled Component의 장점

3-1) component 단위 스타일링

.iaxDju {
    background-color: black;
    width: 100px;
    height: 100px;
    display: none;
}

...

<div class="sc-bdnylx iaxDju"></div>

styled-components로 생성된 Components들을 빌드하면 임의의 클래스 명에 스타일이 적용되어 있는 것을 확인 할 수 있습니다.

 

styled-components는 중복되지 않는 특정 class명을 생성해 스타일을 적용하기 때문에, className이 중복되거나 selector의 우선 적용 순위 때문에 css 스타일링이 혼선을 일으키는 사고를 방지 할 수 있습니다.

3-2) 조건부 스타일링

const StyledDiv = styled.div`
    background-color: black;
    width: 100px;
    height: 100px;
    ${({ login }) => {
      return login ? `display: none` : null;
    }}
`;

return <StyledDiv login={true}></StyledDiv>;

styled-components는 Component의 props를 전달받아 사용하는 것이 가능합니다. 템플릿 리터럴 내에서 javascript를 사용하는 것과 같은 형식이며, 내부에서 선언된 함수는 props를 파라미터로 실행됩니다.

3-3) 확장 스타일링

const Container = styled.div`
    max-width: 600px;
    width: 100%;
    height: 100px;
  `;

  const BlackContainer = styled(Container)`
    background-color: black;
  `;

  const RedContainer = styled(Container)`
    background-color: red;
  `;

  return (
    <>
      <BlackContainer />
      <RedContainer />
    </>
  );

styled-components는 새로운 Component를 선언하는 것 뿐만 아니라, 기존의 Component에 스타일을 추가하는 것도 가능합니다.

 

확장 스타일링을 사용하면 중복된 코드 양을 줄이고, 분산된 스타일을 일관적으로 관리 할 수 있어 유지보수 비용을 줄일 수 있습니다.

 

const MyLink = styled(Link)`
    color: black;
    text-decoration: none;
  `;

return (
    <Router>
      <MyLink to="/main"> 커스텀 링크 </MyLink>
    </Router>
);

기존 Component에 스타일을 추가 할 수 있는 기능 덕분에, 서드 파티 Component와도 호환이 가능합니다. 예를 들면 자주 사용하는 React-router-dom의 Link Component의 경우에도 위와 같은 방법으로 스타일을 적용해 사용 할 수 있습니다.

 

3-4) 중첩 스코프

const StyledDiv = styled.div`
    background-color: black;
    width: 100px;
    height: 100px;
    p {
      color: white;
    }
  `;

return (
    <>
      <StyledDiv>
        <p>Title</p>
      </StyledDiv>
      <p>content</p>
    </>
);

SASS의 중첩 스코프 규칙을 사용할 수 있습니다. 덕분에 내부의 모든 component를 styled-components로 생성하지 않아도, 하위 컴포넌트에게만 적용하고 싶은 스타일을 스코프 형태로 구현 할 수 있습니다. (🚨주의: 모든 SASS 문법이 사용 가능하진 않습니다.)

 

4) Styled Component 설치법

npm install --save styled-components

 

5) 기본 문법

먼저 위에서 설치한 styled-components 패키지에서 styled 함수를 import합니다. styled는 Styled Components의 근간이 되는 가장 중요한 함수로, HTML 엘리먼트나 React 컴포넌트에 원하는 스타일을 적용하기 위해서 사용됩니다.

 

기본 문법은 HTML 엘리먼트나 React 컴포넌트 중 어떤 것을 스타일링 하느냐에 따라 살짝 다릅니다.

 

3-1) HTML 엘리먼트를 스타일링 할 때는 모든 알려진 HTML 태그에 대해서 이미 속성이 정의되어 있기 때문에 해당 태그명의 속성에 접근합니다.

import styled from "styled-components";

styled.button`
  // <button> HTML 엘리먼트에 대한 스타일 정의
`;

 

3-2) React 컴포넌트를 스타일링 할 때는 해당 컴포넌트를 임포트 후 인자로 해당 컴포넌트를 넘기면 됩니다.

import styled from "styled-components";
import Button from "./Button";

styled(Button)`
  // <Button> React 컴포넌트에 스타일 정의
`;

두가지 문법 모두 ES6의 Tagged Template Literals을 사용해서 스타일을 정의합니다. 그리고 styled 함수는 결국 해당 스타일이 적용된 HTML 엘리먼트나 React 컴포넌트를 리턴합니다. ⭐️⭐️⭐️⭐️⭐️

ES6 에서 새롭게 도입된 기능으로 템플릿 리터럴(Template Literals) 이라고 불린다.
백틱(`)  을 사용하여 문자열과 변수를 함께 사용할 수 있어 문자열 처리에 유용한 기능이다.

Tagged Template Literals는 Template Literals를 이용하여 함수의 인자를 파싱하여 넘겨주는 것이다.

 

예를 들어, 다음과 같이 Styled Components로 작성된 JavaScript 코드는

import styled from "styled-components";

styled.button`
  font-size: 1rem;
`;

아래 CSS 코드가 적용된 <button> HTML 엘리먼트를 만들어낸다고 생각하면 쉽습니다.

button {
  font-size: 1rem;
}

 

이런 식으로 Styled Components를 이용해서 JavaScript 코드 안에 삽입된 CSS 코드는 '글로벌 네임 스페이스(Global Name Space)'를 사용하지 않습니다.

다시 말해, 각 JavaScript 파일마다 고유한 CSS 네임 스페이스를 부여해주기 때문에, 각 React 컴포넌트에 완전히 격리된 스타일을 적용할 수 있게 됩니다.

 

이 것은 순수하게 CSS만을 사용했을 때는 누리기 어려웠던 대표적인 CSS in JS의 장점 중 하나 입니다.

 

6) 고정 스타일링

import React from "react";
import styled from "styled-components";

const StyledButton = styled.button`
  padding: 6px 12px;
  border-radius: 8px;
  font-size: 1rem;
  line-height: 1.5;
  border: 1px solid lightgray;
  color: gray;
  background: white;
`;

function Button({ children }) {
  return <StyledButton>{children}</StyledButton>;
}

위에서 배운 Styled Components 문법을 이용해서 간단하게 React로 작성된 버튼 컴포넌트를 스타일링 해보겠습니다.

 

우선 <button> HTML 엘리먼트에 원하는 스타일을 적용한 후 StyledButton 변수에 저장합니다.

이렇게 styled 함수가 리턴하는 것은 위에서 설명드린 것 처럼 React 컴포넌트이기 때문에 JSX를 통해 자유롭게 사용할 수 있습니다.

 

 

이렇게 스타일이 적용된 이 버튼 컴포넌트를 다른 React 컴포넌트에서 다음과 같이 사용할 수 있습니다.

import Button from "./Button";
<Button>Default Button</Button>;

 

브라우저에서 소스 보기를 해보면 다음과 같이 <button> HTML 엘리먼트에 Styled Components가 자동으로 생성해준 클래스 이름이 적용되었음을 알 수 있습니다.

<button class="sc-kgAjT beQCgz">Default Button</button>

 

7) 가변 스타일링 Part. 1

import React from "react";
import styled from "styled-components";

const StyledButton = styled.button`
  padding: 6px 12px;
  border-radius: 8px;
  font-size: 1rem;
  line-height: 1.5;
  border: 1px solid lightgray;

  color: ${(props) => props.color || "gray"};
  background: ${(props) => props.background || "white"};
`;

function Button({ children, color, background }) {
  return (
    <StyledButton color={color} background={background} Î>
      {children}
    </StyledButton>
  );
}
import Button from "./Button";
<Button color="green" background="pink">
  Green Button
</Button>;

Styled Components는 React 컴포넌트에 넘어온 props에 따라 다른 스타일을 적용하는 기능(재사용성 👍🏻)을 제공합니다. Tagged Template Literals을 사용하기 때문에 함수도 문자열 안에 포함시킬 수 있다는 점을 이용하는데요.

 

예를 들어, 버튼의 글자색과 배경색을 props따라 바뀌도록 위에서 작성한 예제 코드를 변경해보겠습니다. 자바스크립트의 || 연산자를 사용하여 props이 넘어오지 않은 경우, 기존에 정의한 기본 색상이 그대로 유지되도록 합니다.

 

여기서 주의할 점은 <Button />에 넘어온, color와 background prop을 <StyledButton/> 컴포넌트로 넘겨줘야 한다는 것입니다. 

 

8) 가변 스타일링 Part.2

import React from "react";
import styled, { css } from "styled-components";

const StyledButton = styled.button`
  padding: 6px 12px;
  border-radius: 8px;
  font-size: 1rem;
  line-height: 1.5;
  border: 1px solid lightgray;

  ${(props) =>
    props.primary &&
    css`
      color: white;
      background: navy;
      border-color: navy;
    `}
`;

function Button({ children, ...props }) {
  return <StyledButton {...props}>{children}</StyledButton>;
}
import Button from "./Button";
<Button primary>Primary Button</Button>;

prop에 따라 바꾸고 싶은 CSS 속성이 위와 같이 하나가 아니라 여러 개일 경우가 있습니다. 이럴 경우, Styled Components에서 제공하는 css 함수를 사용해서 여러 개의 CSS 속성을 묶어서 정의할 수 있습니다.

 

예를 들어, primary prop이 넘어온 경우, 글자색을 흰색, 배경색과 경계색은 남색으로 변경하고 싶다면 다음과 같이 예제 코드를 수정할 수 있습니다.

이번에는 자바스크립트의 && 연산자를 사용해서, primary prop이 존재하는 경우에만 css로 정의된 스타일이 적용되도록 하였습니다.

 

9) Styled의 상속(Extending Styles)

const Box = styled.div`
    background-color: red;
    height: 200px;
    width: 200px;
`;

const Circle = styled(Box)`
    border-radius: 50%;
`;

styled.(태그 종류명) 대신에 styled() 함수를 사용하게 되면 다른 컴포넌트의 스타일을 상속할 수 있습니다 😎

 

별도의 스타일 파일이 존재할 경우, 재정의 하거나 새로운 형태를 만들어야 할 때 다른 사람이 만든 스타일 파일을 직접 가서 의미를 파악하고 다른 코드에 영향을 주지 않게 수정을 한 후에 내가 쓰고 싶은 파일에서 사용해야 합니다.

 

하지만 기존에 있던 Component를 재정의 해서 사용하게 되면 상속한 컴포넌트가 어떻게 만들어져있던지 내가 변경 하고 싶은 부분만 재정의 해서 사용하면 내가 바꾼 곳이 다른곳에 영향을 주는지 안주는지 확인할 필요가 없습니다. 즉 기존의 코드는 신경 쓸 필요가 없다는 뜻입니다.

 

10) Styled Component 전역 스타일링(Global Style)

규모가 있는 웹 애플리케이션을 개발할 때는 개별 컴포넌트가 아닌 모든 컴포넌트에 동일한 스타일을 적용하는 편이 유리한 경우가 있습니다.

 

대표적인 예로 font-family CSS 속성을 들 수 있는데, 여러 컴포넌트에 걸쳐 통일된 글꼴을 사용하고 싶은 경우가 대부분이기 때문입니다.

CSS에서 글꼴 관련 속성은 부모 엘리먼트에서 자식 엘리먼트로 상속(inherit)되기 때문에 <body> 엘리먼트를 대상으로 정의해주면 좋을 것 같습니다.

 

또 다른 예로, 브라우저에 상관없이 일괄적인 스타일을 적용하기 위해서 사용하는 CSS 정규화(normalize)CSS 초기화(reset)를 들 수 있습니다. 이런 종류의 전역 CSS 스타일도 애플리케이션 레벨에서 일괄적으로 적용해주는 것이 이상적일 것입니다.

 

애플리케이션 레벨 스타일을 지원하기 위해서 Styled Components는 createGlobalStyle()라는 함수를 제공하고 있습니다.

// GlobalStyle.jsx

import { createGlobalStyle } from "styled-components";

const GlobalStyle = createGlobalStyle`
  *, *::before, *::after {
    box-sizing: border-box;
  }

  body {
    font-family: "Helvetica", "Arial", sans-serif;
    line-height: 1.5;
  }
`;

export default GlobalStyle;

이렇게 createGlobalStyle() 함수로 생성한 전역 스타일 컴포넌트를 애플리케이션의 최상위 컴포넌트에 추가해주면 하위 모든 컴포넌트에 해당 스타일이 일괄 적용됩니다.

 

// App.jsx

import GlobalStyle from "./GlobalStyle";
import BlogPost from "./BlogPost";

function App() {
  return (
    <>
      <GlobalStyle />
      <BlogPost title="Styled Components 전역 스타일링">
        이번 포스팅에서는 Styled Components로 전역 스타일을 정의하는 방법에
        대해서 알아보겠습니다.
      </BlogPost>
    </>
  );
}

export default App;

 

또한, 빈번하게 사용되는 엘리먼트에 대해서는 애플리케이션 레벨에서 기본 스타일을 정의해주면 편리한 경우가 있습니다.

예를 들어, <h2>와 <p> 엘리먼트에 대한 전역 스타일을 추가해보겠습니다.

 

// GlobalStyle.jsx

import { createGlobalStyle } from "styled-components";

const GlobalStyle = createGlobalStyle`
  *, *::before, *::after {
    box-sizing: border-box;
  }

  body {
    font-family: "Helvetica", "Arial", sans-serif;
    line-height: 1.5;
  }

  h2, p {
    margin: 0;
  }

  h2 {
    font-size: 1.5rem;
  }

  p {
    font-size: 1rem;
  }
`;

export default GlobalStyle;

이렇게 해주면 컴포넌트 레벨에서 스타일해줄 부분이 줄어들게 되어, 여러 컴포넌트에 동일한 스타일을 반복해서 정의할 일이 적어집니다. 뿐만 아니라 전역 스타일을 변경없이 그대로 사용할 경우에는 아예 해당 엘리먼트에 대한 스타일을 생략할 수도 있습니다.

 

11) styled-components에서 애니메이션 사용하기

import styled, { keyframes } from "styled-components";

const rotateAni = keyframes`
    from{
      transform: rotate(0deg);
    }
    to{
      transform: rotate(360deg);
    }
`;

const Box = styled.div`
    background-color: red;
    height: 200px;
    width: 200px;
    animation: ${rotateAni} 1s linear infinite;
`;

styled-components에서 애니메이션을 사용하는 방법은 먼저 keyframes 를 import한 뒤에, 위와 같이 애니메이션 명을 선언하고 keyframes`` 사이에 애니메이션을 넣어주면 됩니다.

 

그리고, 컴포넌트에 적용시키려면 animation : ${애니메이션명} ~ 방식으로 넣어주면 됩니다 :)

 

스타일 컴포넌트에서는 keyframes helper를 사용시 앱 전체에서 사용할 수 있는 고유한 인스턴스를 생성합니다.

 

+ Bonus

<Container as="header"/>

- as props을 사용하여 컴포넌트가 가지는 스타일을 유지한 채로, 엘리먼트를 다른 엘리먼트로 교체할 수 있습니다.

https://styled-components.com/docs/basics#extending-styles

 

styled-components: Basics

Get Started with styled-components basics.

styled-components.com

 

const Input = styled.input.attrs({ required: true, minLength: 10 })``;

- .attrs() 함수를 통해 객체 형태로 태그의 속성을 넣어줌으로써 HTML 태그에 속성을 부여할 수 있습니다.
https://styled-components.com/docs/basics#attaching-additional-props

 

styled-components: Basics

Get Started with styled-components basics.

styled-components.com

const Box = styled.div`
    display: flex;
    justify-content: center;
    align-items: center;
    background-color: red;
    height: 200px;
    width: 200px;
    animation: ${rotateAni} 1s linear infinite;
    span {
      font-size: 50px;
    }
  `;
  
  <Box>
    <span>😎</span>
  </Box>

 

- selector 를 사용해서 styled-component가 아닌 요소들도 스타일링 해줄 수 있습니다.

 

span {
      font-size: 50px;
      &:hover {
        font-size: 100px;
      }
      &:active {
        opacity: 0;
      }
    }
    /* span:hover {
      font-size: 100px;
    }
    span:active {
      opacity: 0;
    } */

- 위와 같이 & 연산자를 사용해서 가상 셀렉터를 지정해줄 수도 있습니다.

 

 const Emoji = styled.span`
    font-size: 50px;
  `;
  
 const Box = styled.div`
    display: flex;
    justify-content: center;
    align-items: center;
    background-color: red;
    height: 200px;
    width: 200px;
    animation: ${rotateAni} 1s linear infinite;

    ${Emoji} {
      &:hover {
        font-size: 100px;
      }
      &:active {
        opacity: 0;
      }
    }
 `;
  
  
 <Box>
   <Emoji as="p">😎</Emoji>
 </Box>

- 위와 같이 as 속성을 부여해서 태그가 바뀌더라도 ${컴포넌트명}으로 태그와는 독립적으로 셀렉터를 지정해줄 수도 있습니다.

 

( 출처 : https://www.daleseo.com/react-styled-components/ https://www.daleseo.com/styled-components-global-style/)

 

Styled Components 전역 스타일링 (Global Style)

Engineering Blog by Dale Seo

www.daleseo.com

 

Styled Components로 React 컴포넌트 스타일하기

Engineering Blog by Dale Seo

www.daleseo.com