본문 바로가기
SPA/REACT

다크모드를 위해서 Context API보다 CSS 변수를 활용하세요

by F.E.D 2022. 10. 30.

다크모드를 관리할 때 일반적으로 Context API를 통해서 전체 테마를 바꾸는 ThemeProvider 기법을 많이 사용하게 됩니다.

여전히 CSS-in-JS는 최근 몇 년동안 CSS 사양이 많이 발전하고 개선되고, 최신 브라우저들도 발전함에 따라(Internet explorer의 변명은 더 이상 충분치 않습니다.) ThemeProvider를 사용하지만 많은 사용 사례에서 많은 이점만 있는 것이 아니라는 것을 깨닫게 됩니다.

 

이모션을 사용한 ThemeProvider의 예시를 살펴보시죠.

 

import * as React from "react";
import styled from "@emotion/styled";
import { ThemeProvider } from "emotion-theming";

const themes = {
  light: {
    colors: {
      primary: "blue",
      background: "white",
    },
  },
  dark: {
    colors: {
      primary: "white",
      background: "#000",
    },
  },
};

const StyledPrimary = styled.div(({ theme }) => ({
  padding: 30,
  color: theme.colors.primary,
  backgroundColor: theme.colors.background,
}));

const ThemeToggler = ({ theme, onClick }) => {
  const switchTheme = "light" === theme ? "dark" : "light";
  return (<div onClick={() => onClick(switchTheme)}>테마 변경</div>);
};

const App = () => {
  const [theme, setTheme] = React.useState("light");
  return (
    <ThemeProvider theme={themes[theme]}>
      <StyledPrimary>프라이머리 테마</StyledPrimary>
      <ThemeToggler
        theme={theme}
        onClick={(switchTheme) => setTheme(switchTheme)}
      />
    </ThemeProvider>
  );
};

export default App;

위의 코드를 보면, 자바스크립트로 대부분 모든 이점을 얻을 수 있다는 것이 매우 좋아보입니다.

ContextAPI를 통해서 테마를 전체적으로 전달 할 필요도 없어보입니다.

 

그러면 CSS변수와 비교를 한 번 해볼까요?

body[data-theme='light'] {
  --colors-primary: gray;
  --colors-background: white;
}
body[data-theme='dark'] {
  --colors-primary: white;
  --colors-background: black;
}
import * as React from "react";
import "./css-variables.css";
import styled from "@emotion/styled";

const StyledPrimary = styled.div({
  padding: 30,
  color: "var(--colors-primary)",
  backgroundColor: "var(--colors-background)",
});

const ThemeToggler = () => {
  const [theme, setTheme] = React.useState("light");
  const switchTheme = "light" === theme ? "dark" : "light";
  
  React.useEffect(() => {
  	document.body.dataset.theme = theme;
  }, [theme])
  
  return (<div onClick={() => onClick(switchTheme)}>테마 변경</div>);
};

const App = () => {
  return (
      <StyledPrimary>프라이머리 테마</StyledPrimary>
      <ThemeToggler />
  );
};

export default App;

훨씬 심플해졌죠?

제어권을 css-variables로 넘기니까, 소스 전체가 훨씬 깔끔해졌습니다.

두 가지 관점에서 가장 핵심적인 부분을 조금 비교해보시죠.

// ThemeProvider
const PrimaryText = styled.div(({theme}) => ({
  padding: 30,
  color: theme.colors.primary,
  backgroundColor: theme.colors.background,
}))

// CSS Variables
const PrimaryText = styled.div({
  padding: 30,
  color: 'var(--colors-primary)',
  backgroundColor: 'var(--colors-background)',
})

CSS 변수 접근 방식의 한 가지 요점은 테마를 수락하고 스타일을 반환하는 함수를 만들 필요가 없다는 것입니다.

// src/theme.js
const theme = {
  colors: {
    primary: 'var(--colors-primary)',
    background: 'var(--colors-background)',
  },
}
export {theme}

// 어떤 곳에서든 사용 가능
import {theme} from 'theme';
const StyledPrimary = styled.div({
  padding: 30,
  color: theme.colors.primary,
  backgroundColor: theme.colors.background,
});

DX(개발 경험) 차이 말고 또 다른 점은 어떤 점이 있는지 확인해보세요.

프로파일링을 해서 두가지 방법을 비교해보세요.

 

ThemeProvider

CSS Variables

CSS 변수 접근 방식으로 렌더링된 유일한 구성 요소는 본문 업데이트를 담당하는 ThemeToggler 구성 요소라는 것을 알 수 있습니다.

UX(사용자 경험)이 완벽하게 작동하면서 DX(개발 경험) 또한 만족스럽습니다.

ThemeProvider 접근 방식을 사용하면 모든 구성 요소의 스타일을 업데이트해야 하며 브라우저는 해당 업데이트를 페인트합니다.

그러나 CSS 변수 접근 방식을 사용하면 스타일을 단일 구성 요소(본문)로 업데이트한 다음 브라우저에서 해당 업데이트를 페인트합니다.

 

물론, CSS Variables만으로 할 수 없는 부분들이 존재합니다.

그럴 때는 선택적으로 취사 선택할 수 있고, 두 방법 모두를 섞어서 사용할 수도 있습니다.

그로 인해서 DX(개발경험)은 더욱 더 올라갈 겁니다 : )

 

 

출처 : https://epicreact.dev/css-variables/

 

 

댓글