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