当サイトは、アフィリエイト広告を利用しています
GatsbyでCSSinJSのEmotion「emotion-theming(ThemeProvider)」と「React useContext」と「useState」
を使ってdarkModeを実装する
EmotionのThemeProviderについては下記記事にまとめています。
またhooksのuseState、useContextについては下記記事にまとめていますので参照ください
基本的にはemotion-themingの機能を使って、darkMode切り替えを実装する。
その切り替えを実現するにあたり、useContextとuseStateを使う。
【theme.jsx】
【ThemeContext.jsx】
【themeProvider.jsx】
useStateで下記を実装する
ThemeContextにuseStateを入れる。
これでどのコンポーネントからでもThemeContext経由で
useStateのモードを切り替えられるようになる
EmotionProviderに「引数のモードで適用するcssを判定しを返す関数」を引数で渡す
※引数のモードにはuseStateのモードを設定する
【コンポーネント】
useContextで下記を取得
モード切り替えを呼び出す
useStateのモードが変わる
EmotionProviderの「引数のモードで適用するcssを判定しを返す関数」の
引数のモードが変わるため返却されるモードcssも切り替わる。
EmotionProviderのthemeが切り替わる
yarn add @emotion/core @emotion/react gatsby-plugin-emotion
dark モード、light モード用のテーマを設定する。
mode を受けて、テーマを返却する関数を実装する
// テーマを定義する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;}}
現在のモード(dark or light)を useContext を使って管理する。
次に作成する ThemeProvider 内の state を Context 内に保持し、どのコンポーネントでも
モード変更メソッド(setColorMode)を呼べば,colorMode を変更できるようにする
// 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.tsximport 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.Providervalue={{colorMode,setColorMode: toggleColorMode,}}>{children}</ThemeContext.Provider></EmotionProvider>);};export default ThemeProvider;
theme を切り替える対象のコンポーネントを
ThemeProvider コンポーネントでラップする
※theme の切り替えは ThemeProvider 配下でしかできない
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;
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;
React useContext と emotion-theming を使ってテーマ切り替え機能を実装する 🌔
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;
下記のように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;