프로젝트에 처음으로 라이트/다크 모드 도입.
글로벌에 css 변수 생성 (generateCssVars)
글로벌에 주입 generateCssVars 결과를 :root, data-theme="dark" 에 병합
테마 제어
useAppTheme.ts
export default function useAppTheme(): ThemeTokens {
const { resolvedTheme, theme } = useTheme()
const current = resolvedTheme ?? theme ?? 'light'
return useMemo(() => {
return (current === 'dark' ? darkColors : lightColors) as ThemeTokens
}, [current])
}
하위 컴포넌트에선 클라이언트 테마가 결정된 후 재렌더링 되면서 실제 테마를 반영.
하지만 부모는 초기 하이드레이션 시점에 (테마값이 정확하지 않은) 값이 고정되었음.
새로고침을 하면 자식은 dark를 유지, 부모 컴포넌트는 light 값 지정
따라서 화면 테마 설정이 깨져보였음
편하게 사용하려고(사실 기존 컨벤션이기도 함)
color: color.red01;
를 따라가기 위해 훅을 호출해서 컬러주입하는 방식
const appTheme = useAppTheme();
color: appTheme.red01;
이런 식으로 맞추려고 하다보니 훅을 호출했는데 부모컴포넌트에서 훅에서 사용하는 useTheme의 resolvedTheme이 undefined라 생기는 문제라고 볼수 있었다.
리터럴을 쓸 때 초기 렌더에서 inline으로 박혔다면, React가 부모를 재렌더하지 않으면 inline 값은 그대로 남습니다.
Emotion이 SSR 시 생성한 클래스에 리터럴 색이 들어가 있으면, 클라이언트에서 data-theme가 바뀌어도 그 CSS 규칙은 그대로 남아 보일 수 있음.
그래서 해결책은 CSS 변수를 쓰는 것이었음.
스타일에는 'var(--color-asset_default)' 같은 참조만 남는데 브라우저는 실제 변수 값을 런타임에 계산해서 적용. 그래서 [data-theme="dark"]에서 변수 값을 바꾸면 그 참조를 사용하는 모든 요소의 색이 즉시 변합니다 - 클래스 재생성 불필요