마이그레이션하고 Storybook 띄우니까 갑자기 이런 에러가 떴다.
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
@emotion/react가 React를 중복으로 번들링하고 있었다. Storybook이랑 라이브러리가 서로 다른 React 인스턴스를 보고 있던 거다.
Vite 설정에서 React를 deduplication 해줬다.
// vite.config.ts
export default defineConfig({
resolve: {
dedupe: ['react', 'react-dom', '@emotion/react'],
},
});
참고: Vite 공식 문서 - resolve.dedupe
MUI 컴포넌트들이 갑자기 터지기 시작했다.
Cannot read properties of undefined (reading 'down')
커스텀 테마만 ThemeProvider에 넘기고 있었는데, MUI가 필요로 하는 breakpoints 같은 게 없었다.
MUI 테마랑 커스텀 테마를 합쳐줬다.
import { createTheme } from '@mui/material/styles';
const muiTheme = createTheme();
const mergedTheme = {
...muiTheme,
...customTheme,
};
<ThemeProvider theme={mergedTheme}>
MUI의 Button이나 다른 컴포넌트들이 색상을 못 찾았다.
MUI 테마 구조(palette.primary.main)랑 우리 커스텀 테마 구조(color.primary)가 달랐다.
MUI palette도 같이 설정해줬다.
const muiTheme = createTheme({
palette: {
primary: { main: customTheme.color.primary },
secondary: { main: customTheme.color.secondary },
},
});
전역 스타일에 스크롤바 커스터마이징을 해뒀는데, Emotion으로 바꾸니까 적용이 안 됐다.
styled-components의 createGlobalStyle이랑 Emotion의 Global 컴포넌트 문법이 달랐다.
// Before (styled-components)
const GlobalStyle = createGlobalStyle`
::-webkit-scrollbar {
width: 6px;
}
`;
// After (Emotion)
import { Global, css } from '@emotion/react';
<Global
styles={css`
::-webkit-scrollbar {
width: 6px;
}
`}
/>
참고: Emotion 공식 문서 - Global Styles
theme.color.primary 이런 거 쓰면 타입 에러가 났다.
@emotion/react 모듈을 확장해줬다.
// styled.d.ts
import '@emotion/react';
import { AppTheme } from './theme';
declare module '@emotion/react' {
export interface Theme extends AppTheme {}
}
참고: Emotion 공식 문서 - TypeScript
원인 찾는게 제일 어려웠다.. ㅜㅜ
Test.tsx에서 스타일 컴포넌트가 있었다:
const SContainer = styled(Container)`
margin-top: 0;
`;
개발자 도구로 확인해보니 보니까 margin-top: 0이 적용이 안 되고 있던 것이다..!
styled-components는 컴포넌트가 정의된 순서대로 CSS를 주입한다. 하지만 Emotion은 컴포넌트가 렌더링되는 순서대로 CSS를 주입한다.
그래서 어떤 일이 생기냐면:
SContainer가 렌더링됨 → CSS 주입Container가 렌더링됨 → CSS 주입기존 styled-components에선 확장한 컴포넌트 스타일이 무조건 나중에 들어가서 우선순위가 높았는데, Emotion에선 반대가 된 거다.
&&를 써서 specificity를 높여줬다.
const SContainer = styled(Container)`
&& {
margin-top: 0;
}
`;
&&는 .class.class 형태의 selector를 만들어서 우선순위를 높여준다.
참고:
| 문제 | 원인 | 해결 |
|---|---|---|
| Storybook useState 에러 | React 중복 번들링 | Vite dedupe 설정 |
| breakpoints.down undefined | MUI 테마 누락 | 테마 머지 |
| palette.primary undefined | MUI palette 미설정 | createTheme으로 palette 설정 |
| 전역 스크롤바 안 먹음 | Global 컴포넌트 문법 차이 | Emotion Global 문법으로 변경 |
| 테마 타입 에러 | 모듈 타입 확장 필요 | declare module로 확장 |
| CSS 우선순위 문제 | 렌더링 순서 기반 주입 | && 로 specificity 증가 |
styled(Component) 확장할 때는 && 쓰는 게 안전하다