๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ“บ Front End/-- react & redux & nextjs

[styled-components] ThemeProvider๋กœ ๊ณตํ†ต ์Šคํƒ€์ผ ์†์„ฑ ๊ด€๋ฆฌํ•˜๊ธฐ.

by Wonit 2021. 1. 2.

์‚ฌ์ดŒ ๋ˆ„๋‚˜์˜ blessmusic.cf

์ง€๋‚œ ์‚ฌ์ดŒ ๋ˆ„๋‚˜์—๊ฒŒ ํ”ผ์•„๋…ธ ์—ฐ์Šต์‹ค ํŽ˜์ด์ง€ ๋งˆ๋ จํ•ด์ฃผ๊ธฐ ํ”„๋กœ์ ํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฉด์„œ ๊ฐ€์žฅ ํฌ๊ฒŒ ๋Š๊ผˆ๋˜ ์ ์€ ๋ฐ”๋กœ css์—์„œ์˜ ํ†ต์ผ์„ฑ์ด ์—†๋‹ค ๋ผ๋Š” ๊ฒƒ์ด์—ˆ๋‹ค.

 

์•ฝ 15๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ์™€ 12๊ฐœ์˜ ์„น์…˜๊ณผ 3๊ฐœ์˜ ํŽ˜์ด์ง€๋กœ ์ด๋ฃจ์–ด์ง„ ๋ฆฌ์•กํŠธ ์•ฑ์„ ๋งŒ๋“ค๋ฉด์„œ ๊ฐ๊ฐ์˜ ์ปดํฌ๋„ŒํŠธ์— ๋งž๋Š” style.js ํŒŒ์ผ์„ ์ž‘์„ฑํ–ˆ๋‹ค.


ํ•˜์ง€๋งŒ ๊ทธ ๋‹น์‹œ์—๋Š” CSS๋ฅผ ๊ณตํ†ต์œผ๋กœ ๊ด€๋ฆฌํ•  ์ƒ๊ฐ๋ณด๋‹ค๋Š” ๋น ๋ฅด๊ฒŒ ํ”„๋กœ์ ํŠธ๋ฅผ ๋๋‚ด๊ณ ์‹ถ์€ ๋งˆ์Œ์ด์—ˆ์–ด์„œ ๊ณตํ†ต ์Šคํƒ€์ผ์— ๊ด€ํ•œ ์ƒ๊ฐ์˜ ์—ฌ์œ ๊ฐ€ ์—†์—ˆ๋‹ค.


๊ทธ๋ž˜๋„ ๋งˆ์Œ ํ•œ์ผ ์— ๋ถˆ์•ˆ์ด ์žˆ์—ˆ๋Š”๋ฐ, ๊ทธ ๋ถˆ์•ˆ์ด ๊ฐ€์ ธ์˜ฌ ๋ฌธ์ œ์ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

  • ๋งค ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฆ๊ฐ€ํ•จ์— ๋”ฐ๋ผ ์Šคํƒ€์ผ ์ฝ”๋“œ์˜ ์ผ๊ด€์„ฑ์ด ๋–จ์–ด์ง„๋‹ค. => ์œ ์ง€๋ณด์ˆ˜ ๋น„์šฉ์˜ ์ฆ๊ฐ€
  • ์Šคํƒ€์ผ ์†์„ฑ๊ฐ’๋“ค์ด ์ค‘๊ตฌ๋‚œ๋ฐฉ์ด๋ผ ์–ด๋–ค ์†์„ฑ์„ ๊ฐ–๊ณ ์žˆ๋Š”์ง€ ์ฝ”๋“œ๋กœ ํ™•์ธ์ด ์–ด๋ ต๋‹ค => ํ˜‘์—…์—์„œ์˜ ๋ฌธ์ œ
  • ์Šคํƒ€์ผ๋งˆ๋‹ค ๋‹ค๋ฅธ ์†์„ฑ์„ ๊ฐ–๊ณ  ์žˆ์–ด์„œ ์žฌ์‚ฌ์šฉ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค => ๋น„ํšจ์œจ์„ฑ ์ฆ๊ฐ€

์ด๋Ÿฐ ๋ฌธ์ œ์ ์„ styled-components๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด context API๋ฅผ ํ†ตํ•ด ํ•œ ๋ฒˆ์— ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ๊ทธ ๋ฐฉ๋ฒ•์ด ๋ฐ”๋กœ ThemeProvider์ด๋‹ค.

 

ThemeProvider

styled-components๋Š” ThemeProvider๋ฅผ ํ†ตํ•ด์„œ ๊ฐ•๋ ฅํ•œ theming ์ „๋žต์„ ์ œ๊ณตํ•œ๋‹ค.

 

์œ„์—์„œ ๋‚ด๊ฐ€ ๋ฌธ์ œ์ ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” context api๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ํ–ˆ๋Š”๋ฐ, ์ด ThemeProvider๊ฐ€ ๋ฐ”๋กœ context ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ชจ๋“  ๋ฆฌ์•กํŠธ ์ปดํฌ๋„ŒํŠธ์—๊ฒŒ theme ์†์„ฑ์„ ์ „๋‹ฌํ•˜๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

 

์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ช‡ ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์ณ์„œ depth๋ฅผ ๊ฐ–๋”๋ผ๋„ ๋ฃจํŠธ์— ThemeProvider๊ฐ€ ์ž๋ฆฌ์žก๊ณ  ์žˆ๋‹ค๋ฉด ๋ชจ๋“  ๋ Œ๋” ํŠธ๋ฆฌ์˜ ์ž์‹์—๋Š” ๋‹ค theme ์†์„ฑ์„ ๊ฐ–๊ฒŒ๋œ๋‹ค.

 

์˜ˆ๋ฅผ ๋“ค์–ด์„œ

import React from "react";
import { ThemeProvider } from "styled-components";

const theme = {
  // ... ์‚ฌ์šฉ์ž ์ •์˜ theme code
}

const Home = () => {
  return (
    <ThemeProvider theme={theme}>
      <Header />
      <Sidebar />
      <HeroSection />
      <Footer />
    </ThemeProvider>
  );
}

๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ์ด ๊ฒฝ์šฐ Home ์ปดํฌ๋„ŒํŠธ์—์„œ ์ตœ์ƒ๋‹จ์˜ ํƒœ๊ทธ๊ฐ€ <ThemeProvider> ์ด๋ฏ€๋กœ ํ•˜์œ„ ์ž์‹์˜ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋Š” <ThemeProvider> ์˜ props๋กœ ๋„˜์–ด๊ฐ€๋Š” theme ๊ฐ’์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

 

์ด์ œ ๊ธฐ๋ณธ ์ปจ์…‰์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์ž.

 

๊ธฐ๋ณธ ์ปจ์…‰ ์ดํ•ดํ•˜๊ธฐ

Context API๋ฅผ ์ด์šฉํ•œ๋‹ค๋Š” ThemeProvider์˜ ๊ธฐ๋ณธ ์ปจ์…‰์„ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด์„œ ๊ฐ„๋‹จํ•œ 2๊ฐœ์˜ ๋ฒ„ํŠผ์„ ๋งŒ๋“ค์–ด ๋ณด์ž.

 

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

const Button = styled.button`
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border-radius: 3px;
`;

const ThemeProviderPrac = () => {
  return (
    <div>
      <Button>Normal 1</Button>
      <Button>Normal 2</Button>
    </div>
  );
};

 

๊ทธ๋Ÿผ ์•„๋ฌด ์Šคํƒ€์ผ์ด ์ ์šฉ๋˜์ง€ ์•Š์€ ๋ฒ„ํŠผ์ด ์ƒ์„ฑ๋œ๋‹ค.

 

<ThemeProvider>๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์ผ๋ฐ˜์ ์ธ Context API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ณผ์ •๊ณผ ๋™์ผํ•˜๋‹ค.

 

๋ชจ๋“  ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ์‹ธ๋Š” Context ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด <ThemeProvider>๋ฅผ Theme์˜ ์Šคํƒ€์ผ ์†์„ฑ์„ ์ ์šฉ๋ฐ›์œผ๋ ค๋Š” ์ปดํฌ๋„ŒํŠธ ๋ Œ๋” ํŠธ๋ฆฌ ์ตœ์ƒ๋‹จ์— ๋ฐฐ์น˜ํ•œ๋‹ค.

 

... ์ƒ๋žต

const ThemeProviderPrac = () => {
  return (
    <div>
      <Button>Normal</Button>
      <ThemeProvider theme={theme}>
        <Button>Themed</Button>
      </ThemeProvider>
    </div>
  );
};

... ์ƒ๋žต

 

๊ทธ๋ฆฌ๊ณ  ์ƒ‰์ƒ ๋น„๊ต๋ฅผ ์œ„ํ•ด ๊ธฐ์กด์— ๋งŒ๋“ค์—ˆ๋˜ Button ์ปดํฌ๋„ŒํŠธ์˜ defaultProps๋„ ์ง€์ •ํ•ด๋ณด์ž.

 

Button.defaultProps = {
  theme: {
    main: "palevioletred",
  }
}

 

์œ„์˜ ๊ณผ์ •์„ ๊ฑฐ์น˜๋ฉด ๊ฒฐ๋ก ์ ์œผ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ์ž‘์„ฑ๋œ๋‹ค.

 

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

const Button = styled.button`
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border-radius: 3px;

  color: ${(props) => props.theme.main};
  border: 2px solid ${(props) => props.theme.main};
`;

Button.defaultProps = {
  theme: {
    main: "palevioletred",
  },
};

const theme = {
  main: "mediumseagreen",
};

const ThemeProviderPrac = () => {
  return (
    <div>
      <Button>Normal</Button>
      <ThemeProvider theme={theme}>
        <Button>Themed</Button>
      </ThemeProvider>
    </div>
  );
};

export default ThemeProviderPrac;

 

๊ทธ๋ฆฌ๊ณ  ์ˆ˜ํ–‰ํ•œ๋‹ค๋ฉด ThemeProvider๋ฅผ ์‚ฌ์šฉํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

 

 

๋” ๋‚˜์•„๊ฐ€์„œ Function Themes๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ

๋” ๋‚˜์•„๊ฐ€์„œ theme props๋ฅผ ๊ฐ์ฒด์™ธ์— ํ•จ์ˆ˜๋กœ ๋„˜๊ธธ ์ˆ˜๋„ ์žˆ๋‹ค.

 

ํ•จ์ˆ˜๋กœ theem props๋ฅผ ๋„˜๊ธด๋‹ค๋ฉด ๋ฌธ๋งฅ์ ์œผ๋กœ ํ…Œ๋งˆ๊ฐ€ ๋งŒ๋“ค์–ด์งˆ ์ˆ˜ ์žˆ์–ด์„œ ๋” ๋‹ค์–‘ํ•œ ์Šคํƒ€์ผ๋ง์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

๊ฐ€๋ณ๊ฒŒ ์ปจ์…‰๋งŒ ๋Š๊ปด๋ณด์ž.

ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•œ <ThemeProvider> ๋ฅผ ์ด์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ 2๊ฐœ์˜ ๋ฒ„ํŠผ์„ ๋งŒ๋“ค๊ณ  ๊ฐ๊ฐ์„ Theme์˜ props๋กœ foreground์˜ fg ์†์„ฑ๊ณผ background์˜ bg ์†์„ฑ ๊ฐ’์„ palevioletred์™€ white๋กœ ์ค˜๋ณด์ž.

 

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

const Button = styled.button`
  color: ${(props) => props.theme.fg};
  background: ${(props) => props.theme.bg};
  border: 2px solid ${(props) => props.theme.fg};

  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border-radius: 3px;
`;

const theme = {
  fg: "palevioletred",
  bg: "white",
};

const ThemeProviderPrac = () => {
  return (
    <div>
      <ThemeProvider theme={theme}>
        <Button>๊ธฐ๋ณธ theme props ์‚ฌ์šฉ</Button>
        <Button>ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•œ new theme</Button>
      </ThemeProvider>
    </div>
  );
};

export default ThemeProviderPrac;

 

๊ทธ๋Ÿผ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋™์ผํ•œ palevioletred ์ƒ‰์„ ๊ฐ€์ง„ ๋ฒ„ํŠผ 2๊ฐœ๊ฐ€ ๋‚˜์˜จ๋‹ค.

 

 

์ด์ œ invertTheme ์ด๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž.

 

const invertTheme = ({ fg, bg }) => ({
  fg: bg,
  bg: fg
});

์ด ํ•จ์ˆ˜๋Š” ๋ณด์ด๋‹ค์‹ถ์ด ์„œ๋กœ ์ƒ‰์„ ์Šค์œ„์นญ ํ•˜์—ฌ theme props๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋œ๋‹ค.

 

ํ•ด๋‹น ํ•จ์ˆ˜์˜ theme props๋ฅผ ์ƒˆ๋กœ์šด ThemeProvider ์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ค์–ด์„œ ๊ฐ์‹ธ๋ณด์ž.

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

const Button = styled.button`
  color: ${(props) => props.theme.fg};
  background: ${(props) => props.theme.bg};
  border: 2px solid ${(props) => props.theme.fg};

  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border-radius: 3px;
`;

const theme = {
  fg: "palevioletred",
  bg: "white",
};

const invertTheme = ({ fg, bg }) => ({
  fg: bg,
  bg: fg,
});

const ThemeProviderPrac = () => {
  return (
    <div>
      <ThemeProvider theme={theme}>
        <Button>๊ธฐ๋ณธ theme props ์‚ฌ์šฉ</Button>
        <Button>ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•œ new theme</Button>
      </ThemeProvider>
    </div>
  );
};

export default ThemeProviderPrac;

 

์šฐ๋ฆฌ๋Š” ์ด์ œ ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด์„œ theme์„ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค

 

ํ”„๋กœ์ ํŠธ์—์„  ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ• ๊นŒ?

๊ทธ๋ ‡๋‹ค๋ฉด ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ• ๊นŒ?


์–ด๋ ต๊ฒŒ ์ƒ๊ฐํ•˜์ง€ ๋ง์ž.

  • ๊ทธ๋ƒฅ ์šฐ๋ฆฌ๊ฐ€ context api๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ๋น„์Šทํ•œ ๋Š๋‚Œ์œผ๋กœ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
  • ๊ณตํ†ต๋˜๋Š” ๊ฐ’๋“ค์„ <ThemeProvider>์—์„œ ๋ฐ˜ํ™˜ํ•ด์ฃผ๊ณ  ์ค‘๋ณต๋œ ์ฝ”๋“œ ์ค„์ธ๋‹ค๋Š” ๋Š๋‚Œ์œผ๋กœ ์“ฐ์ž.

์ด๋ ‡๊ฒŒ๋งŒ ์ด์•ผ๊ธฐ ํ•˜๋ฉด ์ดํ•ด๊ฐ€ ํž˜๋“ค ์ˆ˜ ์žˆ์œผ๋‹ˆ ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ๋ด๋ณด์ž.

 

์˜ˆ๋ฅผ ๋“ค์–ด display:flex๋‚˜ h1์˜ font-size, color๋ฅผ ThemeProvider๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ณตํ†ต ์ž‘์—…์„ ํ•œ๋‹ค๊ณ  ํ•ด๋ณด์ž.

 

theme.js ํŒŒ์ผ์„ ์ƒ์„ฑํ•œ๋‹ค.

import styled from "styled-components";

// ๋ฐ˜์‘ํ˜• ๋””์ž์ธ์„ ์œ„ํ•œ ํ”ฝ์…€ ์ปจ๋ฒ„ํŒ… ํ•จ์ˆ˜
const pixelToRem = (size) => `${size / 16}rem`; 

// font size๋ฅผ ๊ฐ์ฒด๋กœ ๋ฐ˜ํ™˜ํ•ด์ฃผ์ž.
const fontSizes = {
  title: pixelToRem(60),
  subtitle: pixelToRem(30),
  paragraph: pixelToRem(18),
};

// ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ์ƒ‰์„ ๊ฐ์ฒด๋กœ ๋งŒ๋“ค์ž.
const colors = {
  black: "#000000",
  grey: "#999999",
  green: "#3cb46e",
  blue: "#000080",
};

// ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ์Šคํƒ€์ผ ์†์„ฑ์„ theme์œผ๋กœ ๋งŒ๋“ค์–ด๋ณด์ž.
const common = {
  flexCenter: `
    display: flex;
    justify-contents: center;
    align-items: center;
  `,
  flexCenterColumn: `
    display: flex;
    flex-direction: column;
    justify-contents: center;
    align-items: center;
  `,
};

// theme ๊ฐ์ฒด์— ๊ฐ์‹ธ์„œ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
const theme = {
  fontSizes,
  colors,
  common,
};

export default theme;

์œ„์— ํŒŒ์ผ์— ๋‚˜์˜จ ๋‚ด์šฉ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. font-size ํ†ต์ผ
  2. color ํ†ต์ผ
  3. display flex center ์ž๋™ ์ƒ์„ฑ๊ธฐ

๊ทธ๋ฆฌ๊ณ  App.js ํŒŒ์ผ์—์„œ ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  theme๋“ค์„ ์‚ฌ์šฉํ•ด๋ณด์ž.

 

import React from "react";
import styled, { ThemeProvider } from "styled-components";
import theme from "./theme";

const Container = styled.div`
  width: 100vw;
  height: 100vh;
  ${({ theme }) => theme.common.flexCenterColumn};
`;

const Title = styled.h1`
  font-size: ${({ theme }) => theme.fontSizes.title};
  color: ${({ theme }) => theme.colors.grey};
`;

const Subtitle = styled.h2`
  font-size: ${({ theme }) => theme.fontSizes.subtitle};
  color: ${({ theme }) => theme.colors.green};
`;

const Paragraph = styled.p`
  font-size: ${({ theme }) => theme.fontSizes.Paragraph};
  color: ${({ theme }) => theme.colors.blue};
`;

const App = () => {
  return (
    <div>
      <ThemeProvider theme={theme}>
        <Container>
          <Title>Hello</Title>
          <Subtitle>Welcome to styled-component's world</Subtitle>
          <Paragraph>ThemeProvider์— ๋Œ€ํ•ด์„œ ๋ฐฐ์›Œ๋ณผ๊นŒ์š”?</Paragraph>
        </Container>
      </ThemeProvider>
    </div>
  );
};

export default ThemeProviderPrac;

styled-components์—์„œ props๋กœ ๋„˜์˜ค๋Š” ๊ฐ์ฒด ํ˜น์€ ๊ฐ’๋“ค์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ${} ๋ฆฌํ„ฐ๋Ÿด๋กœ ์–ธํŒจํ‚น ํ•ด์ฃผ๋ฉด ๋œ๋‹ค!

 

์œ„์˜ ์ฝ”๋“œ๋Š” ์ข€ ์‹œ๊ฐ„์„ ๊ฐ–๊ณ  ์ฒœ์ฒœํžˆ ํƒ€์ดํ•‘ ํ•ด๋ณด๊ธธ ์ถ”์ฒœํ•œ๋‹ค.

 

๊ทธ๋Ÿผ ๋‹ค์Œ ๊ฐ™์€ ํ™”๋ฉด์ด ๊ณตํ†ต ์†์„ฑ์ด ์ ์šฉ๋˜์–ด ์ž˜ ์ถœ๋ ฅ๋œ๋‹ค.

 

๋Œ“๊ธ€