Styled Components with TypeScript

1. ๊ฐœ์š”

๋ฆฌ์•กํŠธ์—์„œ ์Šคํƒ€์ผ์„ ๋‹ค๋ฃฐ ๋•Œ styled-components๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค. ์ด๋Ÿฐ styled-components๊ณผ Typescript๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„  ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ? ์ด๋ฒˆ ์ฑ•ํ„ฐ์—์„œ๋Š” ํ•ด๋‹น ๋‚ด์šฉ์„ ๋‹ค๋ฃฌ๋‹ค.


2. declaration ํŒŒ์ผ ์„ค์น˜

$ npm i "styled-components"

์œ„์˜ ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด syled-components๋ฅผ ์„ค์น˜ํ•˜๊ณ  ๋ฐ”๋กœ ์Šคํƒ€์ผ์„ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ๊ฒฌํ•œ๋‹ค.

styled-components error1

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์€ ์˜ค๋ฅ˜์—์„œ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค. ์•„๋ž˜์˜ ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์—ฌ styled-components์™€ ๊ด€๋ จ๋œ declarationํŒŒ์ผ์„ ์„ค์น˜ํ•˜๋ฉด ๋œ๋‹ค.

$ npm i --save-dev @types/styled-components


3. ์Šคํƒ€์ผ ์ปดํฌ๋„ŒํŠธ์— ์‚ฌ์šฉ๋˜๋Š” props์˜ ํƒ€์ž… ์ •ํ•˜๊ธฐ

์•„๋ž˜์™€ ๊ฐ™์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜์ž. Layout์€ ์Šคํƒ€์ผ ์ปดํฌ๋„ŒํŠธ๋กœ isRed ๊ฐ’์„ ์ „๋‹ฌ ๋ฐ›์•„ ์ฐธ์ด๋ฉด ๊ธ€์”จ์˜ ์ƒ‰์„ ๋นจ๊ฐ„์ƒ‰์œผ๋กœ ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ํŒŒ๋ž€์ƒ‰์œผ๋กœ ๋‚˜ํƒ€๋‚ธ๋‹ค.

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

const Layout = styled.div`
  color: ${(props) => (props.isRed ? "red" : "blue")};
`;

const Box = ({ children }: React.PropsWithChildren) => {
  const [isRed, setIsRed] = useState(true);
  return (
    <Layout isRed={isRed}>
      <div>Box ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค.</div>
      {children}
    </Layout>
  );
};

export default Box;

Layout๋„ ์Šคํƒ€์ผ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ทผ๋ณธ์€ ์ปดํฌ๋„ŒํŠธ์ด๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— props๋ฅผ ๋ถ€๋ชจ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ›์•„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์œ„์˜ ์˜ˆ์ œ์—์„œ๋Š” isRed๋ผ๋Š” props๋ฅผ ์ „๋‹ฌ ๋ฐ›์€ ๊ฒฝ์šฐ์ด๋‹ค. ํ•˜์ง€๋งŒ ์ „๋‹ฌ๋งŒ ํ–ˆ์„ ๋ฟ ์–ด๋–ค ํƒ€์ž…์˜ props์ธ์ง€ ์ •์˜ํ•˜์ง€ ์•Š์•„ ์˜ค๋ฅ˜๊ฐ€ ๋‚œ ๋ชจ์Šต์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

styled-components props error

๊ทธ๋ ‡๋‹ค๋ฉด Layout์˜ props ํƒ€์ž…์„ ์ •์˜ํ•˜์—ฌ ์˜ค๋ฅ˜๋ฅผ ํ•ด๊ฒฐํ•˜์ž. ๋จผ์ € Layout ์Šคํƒ€์ผ ์ปดํฌ๋„ŒํŠธ prop์˜ ํƒ€์ž…์„ ์ •์˜ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

interface IStyled {
  isRed: boolean;
}

๊ทธ ๋‹ค์Œ ์ •์˜ํ•œ ํƒ€์ž…์„ Layout ์Šคํƒ€์ผ ์ปดํฌ๋„ŒํŠธ๋กœ ์ „๋‹ฌํ•˜์—ฌ Layout ์Šคํƒ€์ผ ์ปดํฌ๋„ŒํŠธ์—์„œ props๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜์ž.

interface IStyled {
  isRed: boolean;
}

const Layout = styled.div<Layout>`
  color: ${(props) => (props.isRed ? "red" : "blue")};
`;

4. ์Šคํƒ€์ผ ์ปดํฌ๋„ŒํŠธ์˜ customMedia์—์„œ props๊ฐ€ ์‚ฌ์šฉ๋  ๋•Œ

sytled components์˜ customMedia์€ ๋ฐ˜์‘ํ˜• ๋””์ž์ธ์„ ํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค. ๋‹น์—ฐํžˆ ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ๋•Œ๋„ ์Šคํƒ€์ผ ์ปดํฌ๋„ŒํŠธ๊ฐ€ props๋กœ ๋ฐ›์€ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๋ช‡๊ฐ€์ง€ ์ถ”๊ฐ€ ์ž‘์—…์„ ํ•ด์•ผํ•œ๋‹ค.

์ฐธ๊ณ ๋กœ customMedia์˜ ๋‚ด์šฉ์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

import { generateMedia } from "styled-media-query";

export const customMedia = generateMedia({
  mobile: "320px",
  tablet: "768px",
  desktop: "1024px",
});

๋จผ์ € ๊ธฐ์กด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ๋Š” ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉ๋˜๋Š”์ง€ ์•Œ์•„๋ณด์ž.

const SLunchmenu = styled.div`
  display: grid;
  ${customMedia.greaterThan("tablet")`
    row-gap: ${(props) => (props.summary ? "5px" : "10px")};
    row-gap: ${(props) => (props.summary ? "0.3125rem" : "0.625rem")};
  `}
`;

SLunchmenu ์Šคํƒ€์ผ ์ปดํฌ๋„ŒํŠธ ๋‚ด์— customMedia.greaterThan()์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ˜์‘ํ˜• ๋””์ž์ธ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋•Œ customMedia.greaterThan()์—์„œ ์‚ฌ์šฉ๋˜๋Š” props๋Š” ์•„๋ž˜์˜ ๋ฐฉ๋ฒ•์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

${(props) => (props.summary ? "5px" : "10px")}

๊ทธ๋ ‡๋‹ค๋ฉด ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ? customMedia.greaterThan()๊ฐ€ ์•„๋‹Œ ์ƒํ™ฉ์—์„œ๋Š” ๋‹จ์ง€ props ํƒ€์ž…๋งŒ ์ •์˜ํ•˜๋ฉด ๋œ๋‹ค. ๋จผ์ € ๊ทธ๋ ‡๊ฒŒ ํ•ด๋ณด์ž.

interface IStyled {
  summary?: string;
}

const SLunchmenu = styled.div<IStyled>`
  display: grid;
  ${customMedia.greaterThan("tablet")`
    row-gap: ${(props) => (props.summary ? "5px" : "10px")};
    row-gap: ${(props) => (props.summary ? "0.3125rem" : "0.625rem")};
  `}
`;

IStyled๋กœ props์˜ ํƒ€์ž…์„ ์ •์˜ํ•˜๊ณ  SLunchmenu ์Šคํƒ€์ผ ์ปดํฌ๋„ŒํŠธ๋กœ ์ „๋‹ฌํ•˜์˜€๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ๋งŒ ์ž‘์„ฑํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ์˜ค๋ฅ˜๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

customMedia props error

summary๊ฐ€ ThemeProps<any> ํƒ€์ž…์— ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์—๋Ÿฌ์ด๋‹ค. ์šฐ๋ฆฌ๋Š” IStyled์—์„œ SLunchmenu์—์„œ ์‚ฌ์šฉํ•˜๋Š” props์˜ ํƒ€์ž…์„ ์ •์˜๋ฅผ ํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ ์—๋Ÿฌ์˜ ๋‚ด์šฉ์„ ๋ณด๋ฉด ํƒ€์ž…์ด customMedia์— ์ œ๋Œ€๋กœ ์ „๋‹ฌ์ด ๋˜์ง€ ์•Š์•˜๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์–ด๋–ป๊ฒŒ customMedia์— props์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์„๊นŒ?

๋ฐฉ๋ฒ•์€ ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•˜๋ฉด ๋œ๋‹ค.

  ${({ summary }) => customMedia.greaterThan("tablet")`
    row-gap: ${summary ? "5px" : "10px"};
    row-gap: ${summary ? "0.3125rem" : "0.625rem"};
  `}

ํ•จ์ˆ˜๋กœ ์ •์˜ํ•˜๋ฉด ๋œ๋‹ค. summary๋ฅผ ๋‹ค์‹œ ํ•œ ๋ฒˆ ๋” ์ธ์ˆ˜๋กœ ์ „๋‹ฌํ•˜๋ฉด customMedia์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.


5. ThemeProvider์˜ theme ํƒ€์ž…

ํ•ด๋‹น ๋ถ€๋ถ„์€ ํ‹ฐ์ฒ˜์บ” ๋ฆฌํŒฉํ† ๋ง์„ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๋‹ค๋ฃจ๊ฒŒ ๋  ๊ฒฝ์šฐ ์ •๋ฆฌํ•œ๋‹ค.


๐Ÿ“… 2022-08-25 - 1. ๊ฐœ์š” ~ 4. ์Šคํƒ€์ผ ์ปดํฌ๋„ŒํŠธ์˜ customMedia์—์„œ props๊ฐ€ ์‚ฌ์šฉ๋  ๋•Œ

Last updated