[ブログの作成]ダークモードの実装(typescript)


🛠 開発環境:nextJS、typescript、emotionJS


超暗モード機能を実現


ダークモード機能が適用されます.
スタイルを
  • 光源モード(明るい背景の暗いテキスト)と暗いモード(暗い背景の明るいテキスト)に分けます.
  • ユーザは、ボタンをクリックして所望のトピックに変更することができる.
  • ページを移動しても、該当するトピックは保持されます.
  • 💄 光源モード/暗いモードスタイルの指定


    まず、光源モードと暗いモードスタイルを指定します.この場合、mainFontsubFont等の対象のキー値は同一でなければならない.
    styles.theme.tsx
    
    export const lightTheme = {
      MAIN: "#6868AD",
      SUB: "#dbd7ff",
      BACKGROUND: "#fdfdff",
      SUBBACKGROUND: "rgb(242, 240, 253)",
    }
    
    export const darkTheme : Theme = {
      MAIN: "#dbd7ff",
      SUB: "#6868AD",
      BACKGROUND: "#202124",
      SUBBACKGROUND: "#30373e",
    }
    次にタイプを指定します.typeofを使用する場合は、指定されたオブジェクト(lightTheme)のpropertyタイプを参照してタイプを宣言できます.
  • let s = "hello";
    let n: typeof s;
    アプリケーション
  • export type Theme = typeof lightTheme;

    すべてのダークモード状態管理-themeProvider


    ページを移動するときに適切なトピックを維持するために、theme providerおよびtheme contextを使用して暗いモード状態管理を行います.
    通常、reactでは、データは親から子供へpropsを介して伝達される.しかし、親から子へではなく曾祖から曾孫へと伝えられると、過程は非常に面倒になる.この場合contextを用いて,各フェーズでpropsを渡す必要がなく,グローバル範囲で値を共有できる.Appの一番上にあるThemeContextサブコンポーネントがContextを介してトピックにアクセスできるようにProviderを追加します.

    React.createContext

  • 公式文書例
  • const MyContext = React.createContext(defaultValue);
    ここで、defalutvalueは、素子が適切なproviderを見つけられない場合に用いられる値である.
    アプリケーション
  • //타입 지정
    interface ContextProps {
      theme: Theme
      toggleTheme: () => void
    }
    
    //객체 생성
    export const ThemeContext = createContext<ContextProps>({
      theme: lightTheme,
      toggleTheme: () => {
        return null
      },
    })

    Context.Provider


    ThemeContextオブジェクトの作成が完了しました.Contextオブジェクトを購読するコンポーネントがContext Providerから現在の値を読み込むようになりました.
  • 公式文書例
  • <MyContext.Provider value={/* 어떤 값 */}>
    providerは、コンテキストの変化を通知し、value propを受信してサブコンポーネントに渡す責任を負います.したがって、プロバイダの値が変更されると、サブスクリプションコンテキストのすべてのコンポーネントが再レンダリングされます.
    アプリケーション
  • // _app.tsx
    
    import type { AppProps } from 'next/app';
    import React, { createContext } from 'react';
    import { Global } from '@emotion/react';
    import { GlobalStyle } from '@styles/global-styles';
    import { lightTheme, darkTheme, Theme } from '@styles/theme';
    import { useDarkMode } from '@hooks/useDarkMode';
    import DarkModeToggle from '@components/Home/DarkModetoggle';
    
    interface ContextProps {
      theme: Theme
      toggleTheme: () => void
    }
    
    //contextX객체 생성
    export const ThemeContext = createContext<ContextProps>({
      //테마와 테마를 변경하는 함수
      theme: lightTheme,
      toggleTheme: () => {
        return null
      },
    })
    
    //useDarkMode hook을 통해 theme과 toggleTheme return;
     const { theme, toggleTheme } = useDarkMode();
    
    function MyApp({ Component, pageProps }: AppProps) {
      const { theme, toggleTheme } = useDarkMode()
      return (
        <ThemeContext.Provider value={{ theme, toggleTheme }}>8
            <Global
              styles={GlobalStyle(theme === lightTheme ? lightTheme : darkTheme)}
            />
            <Component {...pageProps} />
            <DarkModeToggle />
        </ThemeContext.Provider>
      )
    }
    
    export default MyApp

    暗いモードの状態値を保存する:localStorageとnextJS

    // useDarkMode.ts
    
    import { useEffect, useState } from "react";
    import { lightTheme, darkTheme, Theme } from "../styles/theme";
    
    export const useDarkMode = () => {
      const [theme, setTheme] = useState<Theme>(lightTheme);
    
      const setMode = (mode: Theme) => {
        mode === lightTheme
          ? window.localStorage.setItem("theme", "light")
          : window.localStorage.setItem("theme", "dark");
        setTheme(mode);
      };
    
      const toggleTheme = () => {
        theme === lightTheme ? setMode(darkTheme) : setMode(lightTheme);
      };
    
      useEffect(() => {
        const localTheme = window.localStorage.getItem("theme");
        if (localTheme !== null) {
          if (localTheme === "dark") {
            setTheme(darkTheme);
          } else {
            setTheme(lightTheme);
          }
        }
      }, []);
    
      return { theme, toggleTheme };
    };
    
    現在のトピック(theme)と、トピックを変更する関数(toggleTheme)を返すhookが生成される.nextJSはサーバ側レンダリングであるため、windowlocalStoragealertなどに直接近づくとundefinedが吐き出される.ただし、レンダリング後に実行されるuseEffectの性質を利用すれば、localStorageを使用することができる.デフォルトでは、localStorageにlightトピックが保存され、マウスをクリックするたびにトピックが変更されます.その後、ユーザがページを移動またはリフレッシュするときも、localStorageを参照してトピックを保持する.

    暗いモードを切り替え

    
    import LightModeIcon from '@mui/icons-material/LightMode';
    import DarkModeIcon from '@mui/icons-material/DarkMode';
    import styled from '@emotion/styled';
    import React, { ReactElement, useContext } from 'react';
    import { ThemeContext } from '@pages/_app';
    import { lightTheme, Theme } from '@styles/theme';
    import { MEDIA_QUERY_END_POINT } from '@constants/.';
    
    interface ToggleProps {
      theme: Theme;
    }
    
    export default function DarkModeToggle(): ReactElement {
      const { theme, toggleTheme } = useContext(ThemeContext);
    
      return (
        <ToggleButton onClick={toggleTheme} theme={theme}>
          {theme === lightTheme ? (
            <>
              <Emoji>
                <DarkModeIcon aria-label="darkMoon" />
              </Emoji>
              <ModeContent>다크 모드</ModeContent>
            </>
          ) : (
            <>
              <Emoji>
                <LightModeIcon aria-label="lightSun" />
              </Emoji>
              <ModeContent>라이트 모드</ModeContent>
            </>
          )}
        </ToggleButton>
      );
    }
    
    useContextによってProviderに渡されたトピックを購読し、トピックを切り替えます.次に、トピックを変更するToggleTheme関数をbutton構成部品のonClickイベントに接続します.

    ダークモード適用方式1。global


    ダークモードの適用方法は2種類あります.1つ目はglobalstyleを用いてappでスタイルを下げる方法です.
    //global-style.ts
    import { css } from "@emotion/react";
    import { Theme } from "../styles/theme";
    
    export const GlobalStyle = (props: Theme) =>
      css`
        body {
          background: ${props.BACKGROUND};
          color: ${props.MAIN_FONT};
        }
      `;
    //app.ts
    import type { AppProps } from "next/app"
    import React, { createContext } from "react"
    import { Global } from "@emotion/react"
    import { GlobalStyle } from "@styles/global-styles"
    import { lightTheme, darkTheme, Theme } from "@styles/theme"
    import { useDarkMode } from "@hooks/useDarkMode"
    import DarkModeToggle from "@components/Home/DarkModetoggle"
    
    
    export const ThemeContext = createContext<ContextProps>({
      theme: lightTheme,
      toggleTheme: () => {
        return null
      },
    })
    
    function MyApp({ Component, pageProps }: AppProps) {
      const { theme, toggleTheme } = useDarkMode()
      return (
        <ThemeContext.Provider value={{ theme, toggleTheme }}>8
            <Global
              styles={GlobalStyle(theme === lightTheme ? lightTheme : darkTheme)}
            />
            <Component {...pageProps} />
            <DarkModeToggle />
        </ThemeContext.Provider>
      )
    }
    
    export default MyApp

    ダークモード適用方式2。useContextを使用して各構成部品に適用


    2つ目の方法は、useContextを使用して各コンポーネントからcontextを購読することです.チームプロジェクトにはエレメントやスタイル指定変数がたくさんあるので、2つ目の方法を選択しました.
    import { useContext } from 'react';
    import { Theme } from '@styles/theme';
    import { ThemeContext } from '@pages/_app';
    
    interface ThemeProps {
      theme: Theme;
    }
    
    export const ListCard = () => {
      const { theme } = useContext(ThemeContext);
      
        return (
        <Card theme={theme}/>
       )
       
       const Card = styled.article<ThemeProps>`
      background: ${({ theme }) => theme.CARD_BACKGROUND};
      }
    `;
     }

    📸 機能実装画面



    注:リンク1
    メモリンク2