[@types/React]@emotion/styled로 Theme 만들어주기

2022. 9. 1. 19:37React/NFT Airdropper

안녕하세요!

제가 알고 있는 얼마 없는 Web 3.0 지식을 모두에게 나누고자 새로운 웹을 기획했습니다.

제목은 바로 Mintty 하루만에 대충 웹 기획이 끝났네요. 폰트랑 스타일 정의도 대충 끝났구요.

일단 요런 느낌이고 개발하면서 새로 써보는 기술들을 조금씩 정리해보려고 합니다.


NFT 여기 진짜 너무 화려해서 제 스타일이애오....

 

각설하고 클론코딩하면서 css 걱정은 없었는데,

혼자 하니 css부터 뭔가 심상치 않습니다..

 

이번에 새로 찍먹해볼 기술은 @emotion/sytled로 해보는 Theme만들어주기!

아래 블로그에서 보고 영감을 받아서 Typescript로 한번 바꿔봤습니다.

ts에서는 tsconfig라던가 더 설정해줘야하는게 몇몇 개 있더라구요.

또 interface가 업데이트되면서 몇 개 바뀌기도 했구요.

 

 

styled-components 를 emotion 으로 변환하기

지금까지는 CSS-in-JS 인, styled-components 를 사용해왔었다. 최근에 emotion 으로 사용하는 분들이 점점 늘어가는 추세인 것 같아서 나도 이번 기회에 emotion에 대하여 알아보려한다.styled-components와 매우

velog.io

이건 제가 참고한 블로그

 

 

Emotion – TypeScript

Emotion includes TypeScript definitions for @emotion/react and @emotion/styled. These definitions infer types for css properties with the object syntax, HTML/SVG tag names, and prop types. @emotion/react The easiest way to use the css prop with TypeScript

emotion.sh

이건 공식문서

 

 

1. @emotion 설치와 tsconfig.json / .babelrc 설정

새 프로젝트를 시작할 때마다 항상 패키지가 어렵습니다..

일단 저는 이렇게 깔아줬습니다.

npm i @babel/preset-react @emotion/babel-plugin @emotion/core @emotion/react @emotion/styled

 

@emotion/core와 @babel/preset-react는 확실하지 않습니다.

그냥.. 안되는 것 같길래 한 번 깔아봤어요.

2022.09.06 - @emotion/core는 deprecated 되었다고 하네요. 이게 @emotion/react로 업데이트 되었답니다!

 

다음은 tsconfig.json을 만져줘야합니다.

"compilerOptions": {
...
    "jsx": "react-jsx",
    "jsxImportSource": "@emotion/react",
...
},

 

jsxImportSource를 꼭 저렇게 설정해주셔야 나중에 sytled-component에서 props의 타입이 제대로 들어갑니다.

혹여나 props.theme의 하위 object를 제대로 declare 해줬는데도 불구하고 typescript compiler가 제대로 잡아내지 못한다면, 이런 문제일 확률이 높습니다.

 

마지막으로 디렉토리 최상단에 .babelrc를 만들어줍시다!

 

.babelrc

{
  "plugins": ["@emotions"]
  //만약 다른 babel plugin 설치한 경우는 "plugin": ["@emotions", ..otherBabelProps] 나열해서 써주세요.
}

단.. 한줄...!

당연한 이야기지만 다른 babel 플러그인을 설치하셨으면, 계속 나열해서 적어주세요.


2. Theme.ts와 Theme.d.ts 생성

자 이제 패키지는 세팅이 끝났네요.

 

Theme를 만들어봅시다.

Theme은 정의되어있는 interface를 저희가 받아와서 override 해주거나 저희가 추가로 declare 해줘야하는데요.

놀랍게도 내용이 비어있습니다.

ts-safety를 위해서 비워놨다고 공식문서에서 그러네용..

 

간혹 emotion/styled를 처리하다가 이런데에서 오류가 나는 일들이 종종 있습니다.

background-color: ${({ theme }) => theme.color.bgGrey20 };
// TS2339: Property 'colors' does not exist on type 'Theme'.

 

회사다닐땐 이거 때문에 하나 둘씩 globalStyle로 옮겨가다가 이제 더 이상 theme을 안쓰곤 했다는 웃픈이야기가 있는데요.

이 원인 중 하나가 typescript에서 저희가 정의한 Theme의 type을 해독하지 못해서라고 합니다.

결론적으로는 같은 이름의 declare file을 만들어줘서 해결이 가능합니다.

 

theme.tsx

import { Theme } from "@emotion/react";


const theme: Theme = {
  fontSizes: {
    xs: '12px',
    sm: '16px',
    base: '24px',
    md: '36px',
    lg: '48px',
    xl: '64px',
    xxl: '96px',
    xxxl: '128px',
  },
  colors: {
    black: '#12101D',
    white: '#FFFFFF',
    primary: '#B672FD',
    secondary: '#E7266C',
  }
}

export default theme;

 

theme.d.ts

import '@emotion/react';

declare module '@emotion/react' {
  export interface Theme {
    fontSizes: {
      xs: string;
      sm: string;
      base: string;
      md: string;
      lg: string;
      xl: string;
      xxl: string;
      xxxl: string;
    };
    colors: {
      black: string;
      white: string;
      primary: string;
      secondary: string;
    };
  }
}

 

이렇게 해주면 자동으로 웹스톰에서는 자동으로 Nesting이 됩니다.

2022.09.01 declare 파일과 theme.tsx가 연결되지 않아 typescript compiler에서 에러를 확인했습니다.
최하단 추가항목을 봐주세요.

3. Index.tsx에서 Theme 설정

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import theme from "./style/theme";
import { ThemeProvider } from "@emotion/react";

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <ThemeProvider theme={theme}>
      <App/>
    </ThemeProvider>
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

이렇게 해주면 저희가 src 폴더에서 정의해주는 하위 컴포넌트의 styles.tsx에서 이런 식으로 가져오는게 가능합니다.

 

styles.tsx

import styled from "@emotion/styled";

export const WallpaperLayout = styled.div`
  color: ${({ theme }) => theme.colors.black};
`;

 

다만 이러면 문제가 명색에 Theme인데, 하나로 일괄 적용됩니다.

이 Theme가 가장 많이 이용되는게 다크모드/일반 모드 일텐데요.

크게는 세 가지 방법이 생각납니다.

 

1. os 세팅을 보고 테마를 가져오거나,
2. (서버에서 정보를 내려준다면) redux나 swr같은 전역 저장소로 유저가 고른 걸 가져오거나,
3. (사용자가 UI로 직접 설정을 변경해준다면) hooks를 직접 만들어주거나,

 

이렇게 되는데요.

음.. 이 설정은 나중에 한 번 공유하겠습니다.

이번 프로젝트에서 한/영 전환되는 기능도 넣고자 하니.. hooks로 바꾸거나 하는 건 꼭 넣어야하겠네요.

 

고럼 이만!

테마와 함께 즐거운 리액트 되세요!

 

2022.09.01 - 긴급 수정 사항이 있습니다.

 

포스트를 마치고 나서 Theme은 잘 들어오지만 Typescript compiler가 type을 감지하지 못하는 버그를 발견했습니다.

export const WallpaperLayout = styled.div`
  color: ${({ theme }) => {
    console.log(theme);
    return theme.colors.black;
  }};
`;

다음과 같이 콘솔을 찍어봤는데

 

ThemeProvider나 타 설정의 문제가 아니라고 생각합니다.

theme.d.ts를 제대로 binding이 안되었다고 판단했습니다.

 

결론적으로 두 파일을 하나에 합치면 typescript 에러도 나지 않는 것을 확인할 수 있었습니다.

 

theme.tsx(theme.d.ts는 삭제)

import { Theme } from "@emotion/react";

declare module '@emotion/react' {
  export interface Theme {
    fontSizes: {
      xs: string
      sm: string
      base: string
      md: string
      lg: string
      xl: string
      xxl: string
      xxxl: string
    };
    colors: {
      black: string
      white: string
      primary: string
      secondary: string
    };
  }
}

const theme: Theme = {
  fontSizes: {
    xs: '12px',
    sm: '16px',
    base: '24px',
    md: '36px',
    lg: '48px',
    xl: '64px',
    xxl: '96px',
    xxxl: '128px',
  },
  colors: {
    black: '#12101D',
    white: '#FFFFFF',
    primary: '#B672FD',
    secondary: '#E7266C',
  }
}

export default theme;

 

혼선을 드려 죄송합니다.

수정사항 발생 시 바로바로 업데이트하겠습니다.