styled-components

ํ•™์Šต ํ‚ค์›Œ๋“œ

  • Styled Components

    • ์„ค์น˜ ๋ฐ ์„ค์ •

    • ๊ธฐ๋ณธ ๋ฌธ๋ฒ•

    • props

    • attrs

    • Reset CSS

    • Global Style

    • Theme

๐Ÿ’…๐Ÿป styled-components

  • CSS in JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • CSS์˜ ๋ฌธ์ œ์ ์„ ํ•ด๊ฒฐํ•˜๊ณ ์ž ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ๋กœ CSS๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์„ค๊ณ„

  • Tagged Template Literals ๋ฌธ๋ฒ• ์‚ฌ์šฉ

๐Ÿ“– Tagged Template Literals ES6์— ์ถ”๊ฐ€ ๋œ ๋ฌธ๋ฒ•์œผ๋กœ template literals ์˜ ๋”์šฑ ๋ฐœ์ „๋œ ํ•œ ํ˜•ํƒœ๋กœ ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ…œํ”Œ๋ฆฟ ๋ฆฌํ„ฐ๋Ÿด์„ ํ•จ์ˆ˜๋กœ ํŒŒ์‹ฑ ํ•  ์ˆ˜ ์žˆ๋‹ค. ํƒœ๊ทธ ํ•จ์ˆ˜์˜ ์ฒซ๋ฒˆ์งธ ์ธ์ˆ˜๋Š” ๋ฌธ์ž์—ด ๊ฐ’์˜ ๋ฐฐ์—ด์„ ํฌํ•จํ•˜๊ณ , ๋‚˜๋จธ์ง€ ์ธ์ˆ˜๋Š” ํ‘œํ˜„์‹๊ด€ ๊ด€๋ จ๋˜์–ด ์žˆ๋‹ค.

โš™๏ธ ์„ค์น˜ ๋ฐ ์„ค์ •

  • ํŒจํ‚ค์ง€ ์„ค์น˜

    • Babel Plugin์„ SWC์—์„œ ์“ธ ์ˆ˜ ์žˆ๋„๋ก ํฌํŒ…ํ•œ ๊ฒƒ๋„ ํ•จ๊ป˜ ์„ค์น˜ @swc/plugin-styled-components

    • โญ๏ธ SSR ์ง€์› ๋“ฑ์„ ์œ„ํ•œ ๊ณต์‹ ๊ถŒ์žฅ์‚ฌํ•ญ

npm i styled-components

npm i -D @types/styled-components @swc/plugin-styled-components
{
  "jsc": {
    "experimental": {
      "plugins": [
        [
          "@swc/plugin-styled-components",
          {
            "displayName": true,
            "ssr": true
          }
        ]
      ]
    }
  }
}

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

  • sytel.์›ํ•˜๋Š” ํƒœ๊ทธ ์“ฐ๊ณ  ๋ฆฌํ„ฐ๋„๋ฐฉ์‹์œผ๋กœ css ์†์„ฑ๋“ค์„ ๋„ฃ์–ด์ฃผ๋ฉด ๋œ๋‹ค.

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

const Circle = styled.div`
  width: 5rem;
  height: 5rem;
  background: black;
  border-radius: 50%;
`;

function App() {
  return <Circle />;
}

export default App;

props

  • props๋ฅผ ์ด์šฉํ•ด์„œ ํ™œ์„ฑํ™” ์—ฌ๋ถ€๋ฅผ ํ‘œํ˜„ํ•˜๊ฑฐ๋‚˜ ํŠน์ • ์Šคํƒ€์ผ์„ ์žก์•„ ์ฃผ๊ณ  ์‹ถ์„ ๋•Œ ์œ ์šฉ

import { useState } from 'react';

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

type ButtonProps = {
  active?: boolean;
};

const Button = styled.button<ButtonProps>`
  width: 100px;
  height: 100px;
  background-color: #fff;
  color: #000;
  border: 1px solid gray;

  ${(props) =>
    props.active &&
    css`
      background-color: #00f;
      color: #fff;
      border: 1px solid #00f;
    `}
`;

export default function Switch() {
  const [toggle, setToggle] = useState(false);

  const handleClick = () => {
    setToggle(!toggle);
  };

  return (
    <Button onClick={handleClick} active={toggle}>
      ON/OFF
    </Button>
  );
}

attrs

  • ๊ธฐ๋ณธ ์†์„ฑ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋ฐ˜๋ณต๋˜๋Š” ์†์„ฑ์„ ์ฒ˜๋ฆฌํ•  ๋•Œ ์œ ์šฉ

    • ๋ฒ„ํŠผ์„ ๋งŒ๋“ค ๋•Œ ์ ๊ทน์ ์œผ๋กœ ํ™œ์šฉ

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

type ButtonProps = {
  type?: 'button' | 'submit' | 'reset';
};

const Button = styled.button.attrs<ButtonProps>((props) => {
  return {
    type: props.type ?? 'button',
  };
})`
  width: 100px;
  height: 100px;
  background-color: #fff;
  color: #000;
  border: 1px solid gray;
`;

export default Button

Reset CSS

styled-components ์—์„œ Reset CSS ์ฒ˜๋Ÿผ ํƒœ๊ทธ๋“ค์˜ ๊ธฐ๋ณธ ์†์„ฑ์„ ์ดˆ๊ธฐํ™” ํ•˜๊ธฐ ์œ„ํ•ด ์„ค์น˜

npm i styled-reset
  • ์ตœ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ ์ ์šฉ

// App.tsx

import { Reset } from 'styled-reset';

export default function App() {
  return (
    <div>
      <Reset />
    </div>
  );
}

Global Style

  • styles ํด๋” , GlobalStyle.ts ํŒŒ์ผ ์ƒ์„ฑ

mkdir styles
touch styles/GlobalStyle.ts
// GlobalStyle.ts

import { createGlobalStyle } from 'styled-components';

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

  *,
  *::before,
  *::after {
    box-sizing: inherit;
  }

  html {
    font-size: 62.5%; 
  }

  body {
    font-size: 1.6rem;
  }

  :lang(ko) {
    h1, h2, h3 {
      word-break: keep-all;
    }
  }
`;

export default GlobalStyle
  • ์ตœ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ ์ ์šฉ

import { Reset } from 'styled-reset';

import GlobalStyle from '../styles/GlobalStyle';

export default function App() {
  return (
    <div>
      <Reset />
      <GlobalStyle />
    </div>
  );
}

Theme

  • Theming โ†’ <ThemeProvider> ํ™œ์šฉ

  • ๋””์ž์ธ ์‹œ์Šคํ…œ์˜ ๊ทผ๊ฐ„์„ ๋งˆ๋ จํ•˜๋Š” ํ™œ์šฉ

  • ex.๋‹คํฌ๋ชจ๋“œ ๋Œ€์‘ํ•˜๊ธฐ ์‰ฌ์›€

styles/defaultTheme.ts ํŒŒ์ผ ์ƒ์„ฑ

const defaultTheme = {
  colors: {
    background: '#FFF',
    text: '#000',
    primary: '#F00',
    secondary: '#00F',
  },
};

export default defaultTheme;

์—ญ์œผ๋กœ typeof ๋ฅผ ์‚ฌ์šฉํ•ด์„œ Theme ํƒ€์ž… ์ง€์ • โ†’ styles/Theme.ts ํŒŒ์ผ ์ƒ์„ฑ

import defaultTheme from './defaultTheme';

type Theme = typeof defaultTheme;

export default Theme;

styles/darkTheme.ts ํŒŒ์ผ ์ƒ์„ฑํ•ด์„œ Theme ์œผ๋กœ ํƒ€์ž… ์ง€์ •

import Theme from './Theme';

const darkTheme : Theme = {
  colors: {
    background: '#000',
    text: '#FFF',
    primary: '#F00',
    secondary: '#00F',
  },
};

export default darkTheme;

์ตœ์ƒ์œ„ <ThemeProvider> ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ†ตํ•ด ๊ณต๊ธ‰

import { Reset } from 'styled-reset';
import { ThemeProvider } from 'styled-components';

import GlobalStyle from '../styles/GlobalStyle';
import defaultTheme from '../styles/defaultTheme';

import Greeting from './components/Greeting';
import Switch from './components/Switch';

export default function App() {

  const theme = defaultTheme;

  return (
    <ThemeProvider theme={theme}> {/* ๐Ÿ‘ˆ๐Ÿป Theme ๊ณต๊ธ‰ */}
      <Reset />
      <GlobalStyle />
      <Greeting />
      <Switch />
    </ThemeProvider>
  );
}

๊ณต๊ธ‰ ๋ฐ›์€ theme์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„  ํƒ€์ž… ์ง€์ • ํ•„์š” โ†’ styled.d.ts ํŒŒ์ผ ์ƒ์„ฑ

  • ์•„๋ž˜ ์ฝ”๋“œ ์ฒ˜๋Ÿผ ํ•˜๋‚˜์”ฉ ์ง€์ •ํ•ด์ค˜๋„ ๋˜์ง€๋งŒ, ์†์„ฑ๋“ค์ด ์ถ”๊ฐ€๋  ๊ฐ€๋Šฅ์„ฑ์„ ๊ณ ๋ คํ•ด์„œ Theme.ts ํ™œ์šฉ

import 'styled-components';

declare module 'styled-components' {
    export interface DefaultTheme extends Theme {
        colors: { 
            background: string; 
            text: string; 
            primary: string; 
            secondary: string; 
        }
    }
}

๋˜๋Š”

import 'styled-components';
import type Theme from './Theme';

declare module 'styled-components' {
    export interface DefaultTheme extends Theme {}
}

๐Ÿ“Œ Theme ์˜๋ฏธ ์žˆ๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์ฐธ๊ณ 

๐Ÿšจ window.matchMedia Error ๋ฐœ์ƒ

window.matchMedia ๋Š” ์‚ฌ์šฉ์ž์˜ ์‹œ์Šคํ…œ ์„ค์ •์˜ ํ…Œ๋งˆ๋ฅผ ์ธ์‹ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•œ๋‹ค.

Jest ๊ณต์‹๋ฌธ์„œ์—๋Š” src/setupTests.ts ํŒŒ์ผ ํ•ด๋‹น ์ฝ”๋“œ ์‚ฌ์šฉํ•ด์„œ ์ด์Šˆ๋ฅผ ํ•ด๊ฒฐํ•˜๋ผ๊ณ  ์•ˆ๋‚ดํ•˜๊ณ  ์žˆ๋‹ค.

// src/setupTests.ts

Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: jest.fn().mockImplementation(query => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: jest.fn(), // deprecated
    removeListener: jest.fn(), // deprecated
    addEventListener: jest.fn(),
    removeEventListener: jest.fn(),
    dispatchEvent: jest.fn(),
  })),
});

๐Ÿ”— ์ฐธ๊ณ 

Last updated