当サイトは、アフィリエイト広告を利用しています

Emotionでdarkmodeを実装する

作成日:2022月01月23日
更新日:2022年09月19日

GatsbyでCSSinJSのEmotion「emotion-theming(ThemeProvider)」と「React useContext」と「useState」
を使ってdarkModeを実装する

EmotionのThemeProviderについては下記記事にまとめています。

またhooksのuseState、useContextについては下記記事にまとめていますので参照ください

仕組み

基本的にはemotion-themingの機能を使って、darkMode切り替えを実装する。
その切り替えを実現するにあたり、useContextとuseStateを使う。

ロジック

【theme.jsx】

  • theme.jsxでlightモードcss、darkモードcssを実装する
  • theme.jsxで「引数のモードで適用するcss(lightモードcss or darkモードcss)を判定しを返す関数」を作る

【ThemeContext.jsx】

  • 下記をデフォルトコンテキストにする
    • モード
    • モード切り替え

【themeProvider.jsx】

  • useStateで下記を実装する

    • モード
    • モード切り替え
  • ThemeContextにuseStateを入れる。 これでどのコンポーネントからでもThemeContext経由で
    useStateのモードを切り替えられるようになる

  • EmotionProviderに「引数のモードで適用するcssを判定しを返す関数」を引数で渡す
    ※引数のモードにはuseStateのモードを設定する

theme変更フロー

【コンポーネント】

  • useContextで下記を取得

    • モード
    • モード切り替え
  • モード切り替えを呼び出す

  • useStateのモードが変わる

  • EmotionProviderの「引数のモードで適用するcssを判定しを返す関数」の
    引数のモードが変わるため返却されるモードcssも切り替わる。

  • EmotionProviderのthemeが切り替わる

必要なパッケージをインストールする

bash
yarn add @emotion/core @emotion/react gatsby-plugin-emotion

themeを作成する

dark モード、light モード用のテーマを設定する。
mode を受けて、テーマを返却する関数を実装する

theme.jsx
// テーマを定義する
const lightTheme = {
background: '#ffffff',
color: '#000000',
};
const darkTheme = {
background: '#222639',
color: '#f0f5fa',
};
// 使用するテーマを返す関数
// 引数としてはuseStateの値を受け取る
export default function getTheme(colorMode) {
// mode受けてテーマ返す
switch (colorMode) {
case 'light':
return lightTheme;
case 'dark':
return darkTheme;
default:
return lightTheme;
}
}

useContextで現在のモードを管理する

現在のモード(dark or light)を useContext を使って管理する。
次に作成する ThemeProvider 内の state を Context 内に保持し、どのコンポーネントでも
モード変更メソッド(setColorMode)を呼べば,colorMode を変更できるようにする

ThemeContext.jsx
// themeContext.ts
// React useContextで現在のモードを管理する
import { createContext, useContext } from 'react';
// interface ThemeContextType {
// colorMode: ColorMode;
// setColorMode: () => void;
// }
// コンテキストのデフォルト値
const defaultContext = {
colorMode: 'light', // 現在のモードを管理
setColorMode: () => {}, // colorMode書き換え用の関数を渡す
};
// コンテキスト作成
export const ThemeContext = createContext(defaultContext);
// useThemeでコンテキストを取得できるようにする
export const useTheme = () => useContext(ThemeContext);

themeProviderを用意する

  • themeProvider の state を ThemeContext.Provider に格納する
  • EmotionProvider の theme に state を設定する。 こうすることで、state(colorMode)が変わる時に theme が変わるようになる
themeProvider.jsx
// ThemeProvider.tsx
import React, { useState } from 'react';
import { ThemeProvider as EmotionProvider } from '@emotion/react';
import { ThemeContext } from './themeContext';
import getTheme from './theme_emotion';
const ThemeProvider = ({ children }) => {
// useStateでモードを管理する(stateの値をcontextProviderに渡す)
const [colorMode, setColorMode] = useState('light');
// モードを切り替える関数
const toggleColorMode = () => {
// colorMode切り替え用関数
setColorMode(colorMode === 'light' ? 'dark' : 'light');
};
return (
// emotion-themingのthemeProvider
//stateのcolorModeが変わるとthemeが切り替わる
<EmotionProvider theme={getTheme(colorMode)}>
{/* useContextのprivderにモードを管理してるstateを渡し
子のcomponentでsetColorModeでstateを変更した場合、
EmotionProviderに渡しているcolorModeが切り替わる
colorModeが切り替れば、getThemeで取得されるthemeも変わり、
darkmodeまたはlightモードに切り替わる
*/}
<ThemeContext.Provider
value={{
colorMode,
setColorMode: toggleColorMode,
}}
>
{children}
</ThemeContext.Provider>
</EmotionProvider>
);
};
export default ThemeProvider;
  • EmotionProviderのプロティに設定したオブジェクトは子のコンポーネントから参照可能になる

コンポーネントでテーマを切り替える

theme を切り替える対象のコンポーネントを
ThemeProvider コンポーネントでラップする
※theme の切り替えは ThemeProvider 配下でしかできない

home.jsx
import React from "react";
import { graphql, Link } from "gatsby";
import _ from "lodash";
import tw, { css } from "twin.macro";
import BlogCardList from "../components/blogCardList";
import TagList from "../components/tagList";
import Layout from "../layout";
import ThemeProvider from "../layout_emotion/theme_emotionProvider";
const Home = ({ data }) => {
const blogPostList = data.allContentfulBlogPost.edges;
const { group } = data.tags;
return (
<Layout>
<ThemeProvider>
<BlogCardList posts={blogPostList} />
</ThemeProvider>
</Layout>
);
};
export default Home;

表示を切り替えるコンポーネント

styled-component形式で実装する場合

  • props で EmotionProvider の値を受け取れる
BlogCardList.jsx
import React from 'react';
import tw, { css, styled } from 'twin.macro';
import BlogCard from '../blogCard';
import { useTheme } from '../../layout_emotion/themeContext';
// propsでEmotionProviderのthemeを受け取ることができる
const Container = styled.div`
height: 100%;
background: ${props => props.theme.background};
color: ${props => props.theme.color};
`;
const BlogCardList = ({ posts }) => {
const { colorMode, setColorMode } = useTheme();
return (
<>
{/* themeで切り替わる部分 */}
<Container>
<p>
current color mode:
{colorMode}
</p>
<button onClick={setColorMode}>toggle color mode</button>
</Container>
</>
);
};
export default BlogCardList;
  • onClick={setColorMode}でtheme切り替え

参考

React useContext と emotion-theming を使ってテーマ切り替え機能を実装する 🌔

StringStyles形式で実装する場合

  • 引数として EmotionProvider の theme を受け取れる
BlogCardList.jsx
import React from 'react';
import tw, { css, styled } from 'twin.macro';
import BlogCard from '../blogCard';
import { useTheme } from '../../layout_emotion/themeContext';
// theme(名前はなんでもいい)でEmotionProviderのthemeを受け取ることができる
const container = theme =>
css`
height: 100%;
background-color: ${theme.background};
color: ${theme.color};
`;
const BlogCardList = ({ posts }) => {
const { colorMode, setColorMode } = useTheme();
return (
<>
{/* themeで切り替わる部分 */}
<div css={container}>
<p>
current color mode:
{colorMode}
</p>
<button onClick={setColorMode}>toggle color mode</button>
</div>
</>
);
};
export default BlogCardList;
  • onClick={setColorMode}でtheme切り替え

注意

下記のようにthemeの引数と関数としての引数を同時設定することはできないっぽい
※したいなら二つ作るしかなさそう
下記のように関数の形でthemeを引数にして呼び出せば、同時に渡すことができた

themeと引数を同時に渡す
const container = (theme, color) => [
css`
height: 100%;
background-color: ${theme.background};
color: ${color};
`,
];
const BlogCardList = ({ posts }) => {
const { colorMode, setColorMode } = useTheme();
return (
<>
{/* themeで切り替わる部分 */}
<div css={[theme => container(theme, 'red')]}>
<p>
current color mode:
{colorMode}
</p>
<button onClick={setColorMode}>toggle color mode</button>
</div>
</>
);
};
export default BlogCardList;

参考

Emotion を使いこなす

新着記事

タグ別一覧
top