Emotion 사용 중 발생한 DetailedHTMLProps 이슈 해결하기

adultlee·2023년 5월 1일
3
post-thumbnail

이런 분들이 유용할 수 있습니다.

  • @emotion/react 의 css function을 사용하면서 원리에 대해서 의문점을 가진 사람
  • 구글링은 좀 해봤어, 다른 사람은 어떻게 문제를 해결할까?

직면한 문제!

next + typescript + emotion 을 사용하여 새로운 프로젝트를 생성하고 있었습니다.

DetailedHTMLProps<HTMLAttributes, HTMLDivElement>' 형식에 'css' 속성이 없습니다

먼저 새롭게 문제를 풀기 전에 해당 에러 코드를 그대로 복사해서 구글링 해보겠습니다!

검색결과 중 한 곳의 블로그를 방문해 보았습니다.

버전에 대해서 정확히 파악하지는 못하겠지만 저와 거의 동일한 문제를 경험하신것으로 보입니다.

작성자 분께서는 아래의 방법을 사용하셨습니다.

  • tsconfig의 compilerOptions 변경 (작성분께서는 실패)

이제 제가 한번 위의 방식 그대로 사용해보겠습니다.


tsconfig 를 수정한뒤 다시 index.ts의 파일을 수정해보니,

말끔하게 에러가 사라진 것을 확인할 수 있었으며,

local 환경에서도 큰 문제없이 적용 되는것을 확인할 수 있었습니다.
작성하신분은 어떤 문제가 있으셨는지 정확하게 파악할 순 없었으나 제 버전에서는 큰 문제 없이 동작하는 것으로 확인할 수 있었습니다.
아래는 제 환경입니다.

// package.json 
  	"dependencies": {
		"@emotion/react": "^11.10.6",
		"next": "^13.1.1",
		"react": "18.2.0",
		"react-dom": "18.2.0",
		"ui": "workspace:*"
	},

tsconfig의 compilerOptions 변경

tsconfig옵션에는 상당히 많은 compilerOptions가 포함되어 있으며, 다양한 기능을 추가하거나 설정을 진행할 수 있습니다.
우선 tsconfig 의 다양한 기능중에서 본문에 소개된 compilerOptions 중 type에 대한 공부를 진행해보겠습니다.
마찬가지로 구글링을 해보며 진행해봅시다.

우선 제가 검색한 옵션은 다음과 같습니다.

검색의 결과는 다음과 같았습니다.

최상단에 위치한 사이트는 typescriptlang.org 입니다.

사이트에 접근하여 확인해보니 다음과 같은 내용이 적혀 있습니다. 간단하게 해석을 달아보겠습니다.

  • 기본적으로, 표시되는 모든 @type 페키지들은 모두 당신의 compilation 에 포함이된다.
  • 나의 폴더(글에서는 동봉된이라는 의미지만 의역을 가미합니다)의 node_modules/@types 의 페키지들은 모두 푯되는 것으로 간주합니다.
  • 예를들어 ./node_modules/@types/, ../node_modules/@types/, ../../node_modules/@types/ 과 같은 디렉토리 구조를 갖더라도 모두 동일하게 의미합니다.
  • 만약 types가 지정이 된다면, 리스트에 포함된 페키지만이 global scope 로 추가가 됩니다.
  • 예시를 본다면 이 tsconfig.json 파일은 오직 위와 같은 모듈만을 추가합니다.

TIP) 번역tip!

개발자 분들은 영어로 된 글들을 주로 읽고는 합니다. 하지만 모국어를 읽듯 영어를 읽어내는것은 어려우니 번역기의 도움을 종종 받고는 합니다. 저는 보통 페이지 번역을 사용하기 보다는, 이해가 안되는 조금 모호한 문장들을 한 줄씩 번역을 하곤 합니다.

다음 과 같이 번역을 하게 되면, 어느 정도 의역을 가미하여 문장의 의미를 좀 더 쉽게 이해할 수 있습니다.

아하 그렇다면 우리가 추가한 @emotion/react/types/css-props 는 무엇이지🤔

아쉽지만 이번에는 검색만으론 쉽게 원하는 결과가 나오지 않습니다.
우선 @emotion/react 는 우리가 추가했던 라이브러리 입니다. 우리가 찾으려는 @emotion/react/types/css-props 는 어디에서 볼 수 있을까요?
@emotion/react 라이브러리에서 찾아보도록 하겠습니다.

// emotion/packages/react/types/css-prop.d.ts
import {} from 'react' // React 모듈에서 어떤 것도 가져오지 않고, 단순히 React의 타입을 가져오기 위해 사용됩니다.
import { Interpolation } from '@emotion/serialize' //Interpolation 타입은 Emotion 라이브러리에서 제공하는 타입 중 하나로, CSS 스타일을 표현하기 위한 객체를 나타냅니다.
import { Theme } from '.' // Theme은 Emotion에서 사용할 수 있는 테마 객체를 나타내며, 일반적으로 프로젝트에서 사용되는 색상, 글꼴 및 기타 스타일과 관련된 변수를 포함합니다.

  // declare module 'react' 구문은 TypeScript에서 기존 모듈을 확장하기 위해 사용되는 선언 구문입니다. 
  // 이 구문을 사용하여 Attributes 인터페이스를 React 모듈에 확장하고, 이 인터페이스에 css 속성을 추가합니다.
declare module 'react' { // 
  interface Attributes {
    css?: Interpolation<Theme>
  }
}

react 모듈에서 Attributes 인터페이스를 선언합니다. Attributes 인터페이스는 React 컴포넌트의 모든 props가 상속받는 기본 인터페이스입니다. 이 인터페이스에 css prop을 추가합니다.

css prop은 Emotion에서 사용되는 prop으로, CSS 스타일을 정의하는 Emotion의 Interpolation 타입을 값으로 가집니다. 이 Interpolation 타입은 @emotion/serialize 모듈에서 export 되어있으며, Emotion의 스타일 정의 방식인 CSS-in-JS를 지원합니다.

마지막으로, Interpolation 타입에는 Theme 타입을 제네릭으로 지정해줍니다. Theme은 Emotion에서 제공하는 테마 객체의 타입으로, 스타일 정의시 테마를 사용할 수 있습니다.

따라서 이 코드는 React 컴포넌트에서 Emotion을 사용할 때, css prop에 대한 타입을 지정하여 TypeScript에서 타입 체크를 보다 엄격하게 할 수 있도록 해줍니다.

🧐 그렇다면 문제는 뭐였을까?

결국 이 오류 메세지는 TypeScript에서 DetailedHTMLProps 에 HTMLDivElement에 {children : ReactNode; css : SerializedSyleds;} 를 할당하려 했을때, 실패했으며, 특히 css 속성을 추가하려고 시도했지만, HTMLDivElement에는 css 속성이 존재하지 않기 때문에 실패했다는 에러 입니다.

그렇기 때문에 css : SerializedStyles 를 DetaildHtmlProps에 추가해야합니다.

DetailedHTMLProps는 React의 HTML 속성과 이벤트를 모두 포함하는 인터페이스입니다. HTMLDivElement는

요소를 나타내는 HTML 태그의 인터페이스이며, css 속성은 기본적으로 HTML 속성에 포함되어 있지 않기 때문에 오류가 발생합니다.

이 문제를 해결하려면, HTMLDivElement 대신에 React.HTMLProps 또는 React.HTMLAttributes를 사용하여 DetailedHTMLProps 인터페이스에 css 속성을 추가해야 합니다. 이렇게 함으로써, DetailedHTMLProps를 통해서 제공하는 react 기본 기능을 모두 제공하면서도, emotion의 css-props를 추가하기 위해 기능을 추가해 주어야 합니다.

따라서 위의 방법은 tsconfig.json 의 compilerOptions 중 types를 @emotion/react/types/css-prop 을 사용하며, react 기본 제공 타입 + css 타입을 사용할 수 있게 된것입니다.
위의 오픈소스에서 확인할 수 있듯, 여기서 interface Attributes + css 가 포함되었기 때문에 가능한것입니다. 왜냐하면 compilerOptions의 types는 선언된 모듈만을 포함하기 때문에, React 기본 DetailedHTMLProps 가 추가되지 않기 때문입니다.

공식문서에서 소개하는 방법

typescript + emotion 으로 검색하면 가장 상단에는 emotion에서 소개하는 공식적인 방법이 있습니다.


바로 다음과 같이 compilerOptions 를 수정하는 것입니다.
저 또한 다음의 방법을 사용했습니다.

jsxImpoerSource에 @emotion/react를 선언하게 되면,

import { css } from '@emotion/react';

function MyComponent() {
  return (
    <div
      css={css`
        color: red;
        font-size: 24px;
      `}
    >
      Hello, world!
    </div>
  );
}

코드를

  function MyComponent() {
  return (
    <div
      css={`
        color: red;
        font-size: 24px;
      `}
    >
      Hello, world!
    </div>
  );
}

다음과 같이 축약해서 사용할 수 있습니다.
그 이유는 무엇일까요?
jsxImportSource에 @emotion/react 를 선언하게 된다면,
공식문서 에 있는 코드를 통해 jsx 함수를 반환합니다.

// @flow
import * as React from 'react'
import Emotion, { createEmotionProps } from './emotion-element'
import { hasOwnProperty } from './utils'

// $FlowFixMe
// jsx는 React.createElement와 동일한 인터페이스를 가지는 함수로, 
// type과 props를 전달받아서 React 요소를 생성합니다
export const jsx: typeof React.createElement = function (
type: React.ElementType,
props: Object
) {
let args = arguments
//만약 props 객체에 css 속성이 없다면, 
// React.createElement 함수를 그대로 호출합니다. 하지만 props 객체에 css 속성이 있다면, 
// Emotion이라는 커스텀 요소를 사용해서 React 요소를 생성합니다.
if (props == null || !hasOwnProperty.call(props, 'css')) {
  // $FlowFixMe
  return React.createElement.apply(undefined, args)
}

let argsLength = args.length
let createElementArgArray = new Array(argsLength)
createElementArgArray[0] = Emotion
createElementArgArray[1] = createEmotionProps(type, props)

for (let i = 2; i < argsLength; i++) {
  createElementArgArray[i] = args[i]
}

// $FlowFixMe
return React.createElement.apply(null, createElementArgArray)
}
                               

저는 따라서 /@emotion/react의 jsxImportSoucr에 포함하면서 jsx 를 js로 반환 할때,
emotion/react 의 jsx 함수를 사용해서 반호나할 수 있도록 수정하며 문제를 해결했습니다.

0개의 댓글