[react.js]ダークモード(Emotion.js+Next.js+Type Script)

23156 ワード


https://velog.io/@ongdree/ブログの作成-暗いモード-実装
他のプロジェクトでの実装をレビューする投稿を参照してください.

🎯 TL;DR


  • プロジェクトの開始後に暗いモードを迅速に実施することをお勧めします.(コツ🍯)

  • タイトルでは、ライトモード/キーの切り替えでトピックを変更します.

  • ページを移動、リフレッシュしても、アプリケーションのトピックは保持されます.(Context API + localStorage)
  • 1𗞚テーマの色の指定

    // styles/theme.ts
    export const lightTheme = {
      MAIN: 'black',
      SUB: 'white',
      BACKGROUND: '#fdfdff',
    };
    
    export const darkTheme = {
      MAIN: 'white',
      SUB: 'black',
      BACKGROUND: '#202124',
    };
    
    export type ColorTheme = typeof lightTheme;
    
  • MAINSUBは文字の色を指定し、BACKGROUNDは背景の色を指定します.(より多くの色が必要な場合は追加!)

  • ColorTheme typeをtypeofでエクスポートします.
  • 2▼UseDarkMode()を実施する


  • Next.jsでは、レンダリング前にlocalStoragewindowなどに近づくとエラーが発生します.したがって、useEffectの内部でwindow.localStorage.getItem('theme')によってトピックのリフレッシュまたは初回レンダリングが決定される.(default値はlightThemeです.)

  • 初めてlocalStorage.setItemを行ったのは、光線モード->ダークモードが始まったときです.
  • // hooks/useDarkMode.ts
    
    import { useEffect, useState } from 'react';
    import { lightTheme, darkTheme, ColorTheme } from '../styles/theme';
    
    export const useDarkMode = () => {
      // 1. 초기 colorTheme은 lightTheme를 가진다.
      const [colorTheme, setColorTheme] = useState<ColorTheme>(lightTheme);
      
      // 4. state의 값도 변경 + local 저장 값도 변경
      const setMode = (mode: ColorTheme) => {
        mode === lightTheme
          ? window.localStorage.setItem('theme', 'light')
          : window.localStorage.setItem('theme', 'dark');
        setColorTheme(mode);
      };
    
      // 3. 사용자가 toggleColorTheme을 하면 setMode를 통해 기존의 colorTheme과 반대 값을 저장한다.
      const toggleColorTheme = () => {
        colorTheme === lightTheme ? setMode(darkTheme) : setMode(lightTheme);
      };
    
      // 2. 마운트 되면 localStorage에 'theme'이 있는지 찾는다.
      // - 새로고침시 다크모드/라이트모드 바로 적용
      // - 페이지 로드가 처음이면 이 과정은 무시된다
      useEffect(() => {
        const localTheme = window.localStorage.getItem('theme');
        if (localTheme !== null) { // localTheme이 존재한다면
          if (localTheme === 'dark') {
            setColorTheme(darkTheme);
          } else {
            setColorTheme(lightTheme);
          }
        }
      }, []);
    
      return { colorTheme, toggleColorTheme };
    };

    3朕暗モード状態管理


    Context APIを使用して、propsの転送を続行しないようにします.ThemeContext.Providerでは、サブコンポーネントがContextを介してトピックにアクセスし、ページを移動するときにトピックを保持できるようにします.
    Next.jsでは最上階が_appなので、ここで設定します.
    // pages/_app.tsx
    
    import React, { createContext } from 'react';
    import type { AppProps } from 'next/app';
    import { Global } from '@emotion/react';
    import { lightTheme, darkTheme, ColorTheme } from '../styles/theme';
    
    // createContext 타입지정
    interface ContextProps { 
      colorTheme: ColorTheme;
      toggleColorTheme: () => void;
    }
    
    // Context 생성
    export const ThemeContext = createContext<ContextProps>({
      colorTheme: lightTheme, // 초기 값으로 lightTheme를 넣어줍니다.
      toggleColorTheme: () => { // light || dark mode를 토글합니다.
        return null
      },
    })
    
    function MyApp({ Component, pageProps }: AppProps) {
      // ❗️useDarkMode hook을 통해 theme과 toggleTheme return;
     const { theme, toggleTheme } = useDarkMode();
      
      return (
        // Provider은 context의 변화를 알리는 역할을 합니다.
        // toggleTheme를 통해 theme이 변경되면 하위 컴포넌트들은 모두 리렌더링됩니다.
        <ThemeContext.Provider value={{ theme, toggleTheme }}>
            <Component {...pageProps} />
        </ThemeContext.Provider>
      )
    }
    
    export default MyApp

    4πダークモード使用と切り替えボタン

  • useDarkModeサブスクリプションを使用して、ThemeContextによって作成され、colorThemeの~toggleColorThemeおよびuseContextに伝播する.
  • toggleColorThemeを起動するには、ボタンをクリックします.

  • cssは、導入されたcolorThemeによって適用される.
  • // components/Header/HeaderBtns/DarkModeToggle/index.tsx
    
    import React, { ReactElement, useContext } from 'react';
    import { ThemeContext } from '../../../../pages/_app';
    import styled from '@emotion/styled';
    import { lightTheme, ColorTheme } from '../../../../styles/theme';
    
    interface ToggleProps {
      colorTheme: ColorTheme;
    }
    
    const DarkModeToggle = () => {
      // 1. useContext를 통해서 colorTheme, toggleColorTheme를 구독한다
      const { colorTheme, toggleColorTheme } = useContext(ThemeContext);
    
      return (
        // 2. 버튼을 클릭하면 toggleColorTheme을 작동한다
        <ToggleButton onClick={toggleColorTheme} colorTheme={colorTheme}>
          {colorTheme === lightTheme ? '다크 모드' : '라이트 모드'}
        </ToggleButton>
      );
    }
    
    // 3. colorTheme을 prop으로 가져와 css를 적용한다.
    const ToggleButton = styled('button')<ToggleProps>`
      display: flex;
      color: ${({ colorTheme }) => colorTheme.MAIN};
      cursor: pointer;
      background: ${({ colorTheme }) => colorTheme.BACKGROUND};
      box-shadow: 3px 3px 10px rgb(0 0 0 / 20%);
    
      &:hover {
        filter: brightness(${({ colorTheme }) => (colorTheme === lightTheme ? '0.9' : '1.13')});
      }
    `;
    
    export default DarkModeToggle;