테마 적용
- 퍼즐 사이트에 다채로운 색을 주기 위하여 여러 가지 테마를 적용해보았다.
- 테마 색상은
핑크
, 민트
, 실버
, 다크
다.
- css 라이브러리는
styled-components
를 사용하였다.
localStorage
테마 저장
- 테마는 클라이언트에서 저장하고 있어야 한다. 따라서
localStorage
에 저장하였다.
테마 적용은 4개의 파일로 구성된다.
theme.ts
: 테마에 따른 색상들을 담아 놓는다.
ThemeProvider.tsx
: styled-components
에서 제공하는 컴포넌트를 만들어 테마를 적용시킨다.
useTheme.ts
: 테마를 변경할 수 있는 함수다. window.localStorage
를 사용하여 테마를 localStorage
에 저장 또는 가져온다.
_app.tsx
: ThemeProvider
컴포넌트로 공통 레이아웃을 감싼다.
code
const colors = {
dark: 'rgb(43, 42, 43)',
silver: 'rgb(233, 230, 228)',
white: 'rgb(255, 255, 255)',
};
export const silverTheme: Theme = {
bgColor: colors.silver,
textColor: colors.dark,
};
export const darkTheme: Theme = {
bgColor: colors.dark,
textColor: colors.white,
};
export const theme = {
darkTheme,
silverTheme,
colors,
};
import { silverTheme, darkTheme, pinkTheme, mintTheme } from './theme';
import React, { useState, useMemo } from 'react';
import { ThemeContext, ThemeProvider as StyledProvider } from 'styled-components';
interface Props {
children: React.ReactNode;
}
const THEME: {
[key in ThemeKey]: Theme;
} = {
pink: pinkTheme,
dark: darkTheme,
silver: silverTheme,
mint: mintTheme,
};
const ThemeProvider = ({ children }: Props) => {
const [themeMode, setThemeMode] = useState<ThemeKey>('pink');
const themeObject = useMemo(() => {
return THEME[themeMode];
}, [themeMode]);
return (
<ThemeContext.Provider value={{ themeMode, setThemeMode }}>
<StyledProvider theme={themeObject}>{children}</StyledProvider>
</ThemeContext.Provider>
);
};
export { ThemeProvider };
import { useCallback, useContext, useEffect, useLayoutEffect, useMemo } from 'react';
import { ThemeContext } from 'styled-components';
function useTheme() {
const themeKeys: ThemeKey[] = useMemo(() => ['pink', 'silver', 'mint', 'dark'], []);
const context = useContext(ThemeContext);
const { themeMode, setThemeMode } = context;
const canUseDOM = typeof window !== 'undefined';
const useIsomorphicLayoutEffect = canUseDOM ? useLayoutEffect : useEffect;
const setTheme = useCallback(
(theme: ThemeKey) => {
setThemeMode(theme);
window.localStorage.setItem('localTheme', theme);
},
[setThemeMode]
);
useIsomorphicLayoutEffect(() => {
const localTheme = window.localStorage.getItem('localTheme') as ThemeKey;
if (themeKeys.includes(localTheme)) {
setTheme(localTheme);
}
}, [setTheme, themeKeys]);
return [themeMode, setTheme];
}
export default useTheme;
function MyApp({ Component, pageProps }: AppProps) {
return (
<ThemeProvider>
<Component {...pageProps} />
</ThemeProvider>
);
}
export default MyApp;
설명 및 문제점
useTheme
에서 useLayoutEffect
를 사용하여 렌더링 전에 테마를 localStorage
에서 받아와 적용 시켜준다. 따라서 새로 고침 시 기본 테마가 깜빡거리는 현상은 발생하지 않는다.
useLayoutEffect
를 useIsomorphicLayoutEffect
로 커스텀하여 SSR 적용에 따른 에러는 발생하지 않는다.
- 하지만 문제점이
두 가지
발생한다.
문제점
- 페이지를 이동하여 처음 렌더링 될 때 기본 테마인 핑크 테마가 잠깐 보여진다.
useTheme.ts
내부에서 localStorage
에 접근하다 보니 setTheme
를 사용하지 않는 페이지에는 테마 적용이 되지 않는다.
해결 방법
_document.tsx
에서 localStorage
에 접근하여 테마 적용한다.
- 테마를
cookie
에 저장 후, _app.tsx
의 getInitialProps
에서 cookie
에 접근하여 SSR 적용한다.
- SSR을 지원하는 Nextjs를 사용하니 두 번째 방법이 더 깔끔할 것 같아, 두 번째 방법을 채택하여
cookie
에 저장하여 SSR 적용 시키기로 하였다.
cookie
테마 저장
테마 적용은 4개의 파일로 구성된다.
theme.ts
: 테마에 따른 색상들을 담아 놓는다.
ThemeProvider.tsx
: styled-components
에서 제공하는 컴포넌트를 만들어 테마를 적용 시킨다. useState
의 값을 테마로 사용한다. 해당 값의 초기 값은 cookie
에 저장되어 있는 값이다.
useTheme.ts
: 테마를 변경할 수 있는 함수다. 테마 변경 시 cookie
의 값과 useState
의 값을 변경 시켜준다.
_app.tsx
: ThemeProvider
컴포넌트로 공통 레이아웃을 감싼다. getInitialProps
로 cookie
의 값을 받아와 ThemeProvider
컴포넌트로 전달한다.
code
const ThemeProvider = ({ children, pageTheme }: Props) => {
const [themeMode, setThemeMode] = useState<ThemeKey>(pageTheme);
const themeObject = useMemo(() => {
return THEME[themeMode];
}, [themeMode]);
return (
<ThemeContext.Provider value={{ themeMode, setThemeMode }}>
<StyledProvider theme={themeObject}>{children}</StyledProvider>
</ThemeContext.Provider>
);
};
export { ThemeProvider };
import { setCookie } from 'cookies-next';
function useTheme() {
const context = useContext(ThemeContext);
const { themeMode, setThemeMode } = context;
const setTheme = useCallback(
(theme: ThemeKey) => {
setThemeMode(theme);
setCookie('localTheme', theme);
},
[setThemeMode]
);
return [themeMode, setTheme];
}
export default useTheme;
function MyApp({ Component, pageProps }: AppProps) {
return (
<ThemeProvider pageTheme={pageProps.theme}>
<Component {...pageProps} />
</ThemeProvider>
);
}
MyApp.getInitialProps = async ({ ctx, Component }: {ctx: any; Component: any;}) => {
const themeKeys: ThemeKey[] = ['pink', 'silver', 'mint', 'dark'];
let pageProps: any = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
const cookie = ctx.req.cookies;
if (cookie) {
const localTheme = getCookie('localTheme', ctx) as ThemeKey;
if (themeKeys.includes(localTheme)) {
Object.assign(pageProps, {
theme: localTheme,
});
} else {
Object.assign(pageProps, {
theme: 'pink',
});
}
}
return { pageProps };
};
export default MyApp;
결과
useTheme.ts
내부에서만 localStorage
에 접근하여 테마를 적용 시키는 방식이 _app.tsx
에서 테마를 SSR 적용 시키는 방식으로 바껴서 모든 페이지에 코드 추가 없이 테마 적용이 되었다.
cookie
의 값을 가지고 테마에 SSR 적용하여 화면이 처음 렌더링 될 때 기본 테마가 잠깐 보이는 것이 사라졌다.
- 네트워크를 확인해 보면 테마가 SSR이 적용되는 것을 볼 수 있다.
- mint theme
- dark theme