Ayden's journal

styled components

기본 형태

styled 객체

styled components를 사용해 컴포넌트를 만들 때는 변수명이 컴포넌트의 이름이 되며, styled 객체에 존재하는 다양한 HTML 프로퍼티를 가져다 사용한다. 백틱을 통해 각 프로퍼티에 css 스타일을 작성해줄 수 있다. 

import styled from 'styled-components';

const Button = styled.button`
  border: none;
  padding: 16px;

  &:hover,
  &:active {
    background-color: #463770;
  }
`;

export default Button;

css 스타일을 작성할 때 nesting 문법을 사용하여 하위 노드는 물론, & 키워드를 통해 pseudo selector등을 사용할 수 있다.

 

컴포넌트 선택자

${ … } 와 같은 형태의 컴포넌트 선택자를 사용하면 특정 컴포넌트의 하위 노드에 존재하는 컴포넌트에 대해 스타일을 적용할 수 있다.

const Container = styled.div`
  margin: 0 auto;
  width: 400px;

  ${Input} {
  box-sizing: border-box;
  display: block;
  margin: 8px 0 16px;
  width: 100%;
}
  &hover {
    ${Input} {
    background-color: #ccc
    }
  }
`;

 

 

다이내믹 스타일링

변수 사용

const SIZES = {
  large: 24,
  medium: 20,
  small: 16
};

const Button = styled.button`
  ...
  font-size: ${SIZES['medium']}px;
`;

JS에서 탬플릿 문자열에 변수를 그대로 넣는 것처럼, 원하는 값을 미리 지정해두고 필요할 때마다 불러와 사용할 수 있다. 다만 이것은 탬플릿 문자열 그 자체는 아니기 때문에  Styled Components가 내부적으로 처리해 주는 props를 받아 사용할 수 있다. 당연하게도 구조 분해Destructuring하여 가독성 좋게 쓸 수도 있다.

 

함수 사용

const Button = styled.button`
  ...
  font-size: ${(props) => SIZES[props.size]}px; // 프롭을 받는 함수
  font-size: ${({ size }) => SIZES[size]}px; // 구조분해
`;

 

논리 연산자 사용

논리 연산자를 사용하면 JSX에서 조건부 랜더링하듯 조건부 스타일을 적용해줄 수도 있다.

const Button = styled.button`
  ...
  ${({ round }) => round && `border-radius: 9999px;`} // 논리 연산
  border-radius: ${({ round }) => round ? `9999px` : `3px`}; // 삼항 연산
`;

 

 

스타일 재사용

styled() 함수 사용

styled() 함수를 사용하면 다른 컴포넌트의 스타일을 가져와서 몇 가지 스타일만 추가해 원하는 컴포넌트를 만들 수 있다.

const Button = styled.button`
  background-color: #6750a4;
  border: none;
  color: #ffffff;
`;

const SubmitButton = styled(Button)`
  display: block;
  margin: 0 auto;
  width: 200px;
`;

다만 이렇게 만들어지는 컴포넌트는 결국 button 태그로 만들어질거라서, a 태그를 버튼처럼 만드는 데에는 다른 방식이 필요해진다.

 

JSX로 직접 만든 컴포넌트에 styled() 사용

Styled Components는 내부적으로 className을 따로 생성하는 방식으로 스타일을 적용한다. 따라서 JSX로 만든 리액트 컴포넌트에 스타일을 입히고 싶다면 className 값을 prop을 통해 따로 내려주어야 한다.

// JSX로 만든 CustomButton 컴포넌트
const CustomButton = ({ className, children }) => {
  return <button className={className}>{children}</button>;
};

우선 이런 컴포넌트가 있다고 해보자.  이 컴포넌트에 styled() 함수를 사용하여 스타일을 입혀주고 싶다면 아래와 같은 방식을 사용해야 한다.

import styled from 'styled-components';

// styled() 함수를 사용하여 컴포넌트에 스타일을 적용
const StyledButton = styled(CustomButton)`
  padding: 10px 20px;
  border: none;
  border-radius: 4px;

  &:hover {
    background-color: #2980b9;
  }
`;

위 코드의 가장 중요한 부분인 const StyledButton = styled(CustomButton)는 CustomButton이라는 JSX 컴포넌트를 가져다가 스타일을 입하고, 그걸 StyledButton이라는 컴포넌트로서 호출한다는 의미가 된다.

// 위에서 생성한 스타일이 적용된 StyledButton 컴포넌트 사용
const App = () => {
  return <StyledButton>Click me!</StyledButton>;
};

 

 

중요한 점은 맨 위에서 말한 것처럼, 내부적으로 className을 따로 생성하는 방식으로 스타일을 적용하기 때문에 JSX 컴포넌트가 반드시 className이라는 프롭을 받아주어야 한다는 점이다.

 

CSS 함수

멘토님께서 입에 달고 사시는 이야기처럼, 반복되는 코드는 한 곳에서 통합적으로 관리할 수 있어야 한다. 이럴 때 CSS 함수가 사용된다.

import styled, { css } from 'styled-components';

// 재사용할 스타일 정의
const commonFontStyle = css`
  color: #333;
  font-size: 16px;
`;

const fontSize = css`
  font-size: ${({ size }) => SIZES[size] ?? SIZES['medium']}px;
`;

이렇게 특정 스타일이나, 스타일을 처리하는 함수를 css 키워드를 통해 재사용할 수 있는 하나의 함수로 묵어놓고 아래와 같이 호출하여 사용하면 된다.

 

const StyledParagraph = styled.p`
  ${commonFontStyle};
  ${fontSize};
  margin-bottom: 10px;
`;

조심해야 할 부분은 fontSize 처럼 size prop을 받는 스타일 함수의 경우 StyledParagraph 컴포넌트를 사용할 때 size prop을 지정해주어야 한다는 점이다.

 

 

 

 

 

 

 

 

전역 스타일 적용

import { createGlobalStyle } from 'styled-components';

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

  body {
    font-family: 'Noto Sans KR', sans-serif;
  }
`;

createGlobalStyle 함수를 통해 전역으로 설정할 스타일을 등록할 수 있다.

 

keyframes 함수

애니메이션을 만들 때 움직임의 기준이 되는 프레임만 만들고 그 사이의 프레임들을 자동으로 채워 넣는 방식을 주로 사용하는데, 이때 '움직임의 기준이 되는 프레임'을 '키프레임'이라고 부른다.

CSS에서 키프레임은 CSS 애니메이션을 만들 때 기준이 되는 지점을 정하고, 적용할 CSS 속성을 지정하는 문법을 뜻한다. styled components에서는 keyframes 함수를 이용해 이러한 기준점을 정해주게 된다.

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

const placeholderGlow = keyframes`
  20% {
    opacity: 0.7;
  }

  50% {
    opacity: 0.2;
  }
  
  80% {
    opacity: 0.7;
  }
`;

const Placeholder = styled.div`
  animation: ${placeholderGlow} 2s ease-in-out infinite;
`;

function App() {
  return (
    <div>
      <Placeholder>
        <A />
        <B />
      </Placeholder>
    </div>
  );
}

keyframes 함수로 정해준 키프레임은 animation 속성에서 사용되게 된다. 키프레임 외에도 animation-duration(한 번의 동작에 걸리는 시간), animation-timing-function(애니메이션 효과의 시간당 속도를 설정), animation-iteration-count(애니메이션 반복 횟수, infinite면 멈추지 않아 뽀이) 등을 정해줄 수 있다.

이렇게 만들어진 애니메이션 컴포넌트의 하위에 배치된 컴포넌트들은 모두 애니메이션의 적용을 받는다. 위의 예시에서 A 컴포넌트와 B 컴포넌트는 Placeholder 애니메이션 컴포넌트의 적용을 받는 것이다. 그게 아니라 단일 컴포넌트에 넣어준다면, 그냥 animation 속성을 해당 컴포넌트에 넣어주면 그만이다.

 

ThemeProvider

Styled Components에서도 Context를 기반으로 테마를 사용할 수 있다. Context를 내려주는 컴포넌트로는 ThemeProvider라는 걸 사용하게 된다.

const THEMES = {
  light: {
    backgroundColor: '#ffffff',
    color: '#000000',
  },
  dark: {
    backgroundColor: '#121212',
    color: '#ffffff',
  },
}; // theme state에 넣어줄 객체 목록

function App() {
  const [theme, setTheme] = useState(THEMES['light']); // theme state에 객체 넣어줌

  const handleColorChange = (e) => {
    const nextThemeName = e.target.value;
    setTheme(THEMES[nextThemeName]);
  };

  return (
    <ThemeProvider theme={theme}> // theme prop을 사용해 객체를 아래로 내려줌
      <select value={theme.primaryColor} onChange={handleColorChange}>
        <option value="light">light</option>
        <option value="dark">dark</option>
      </select>
      <Button>확인</Button>
    </ThemeProvider>
  );
}

export default App;

ThemeProvider라는 Context Provider를 사용해서 theme이라는 객체를 내려준다. 이렇게 하면 ThemeProvider 안에 있는 Styled Components로 만든 컴포넌트에서는 Props를 사용하듯이 theme이라는 객체를 사용할 수 있다.

const Button = styled.button`
  background-color: ${({ theme }) => theme.backgroundColor};
  /* ... */
`;

 

컴포넌트 태그 변경

const Button = styled.button`
  /* ... */
`;
<Button href="https://example.com" as="a">
  LinkButton
</Button>

 

styled.button으로 선언한 컴포넌트와 스타일과 프로퍼티가 같지만, 태그만 button에서 a로 변경해서 사용하고 싶다면 as prop을 사용하여 해결할 수 있다. 굳이 버튼 모양의 링크 컴포넌트를 하나 더 만들 필요가 없는 것이다.

블로그의 정보

Ayden's journal

Beard Weard Ayden

활동하기