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

emotion-themingを利用して、テーマ切り替え機能を実装します。Twitterのと一緒です。ボタン押して、toggleできるものを目指します。

Install

とりあえず

npx create-react-app dark-mode --typescript

emotion

yarn add @emotion/core @emotion/styled emotion-theming

モード切り替え

React.Contextに今どのモードなのかを保存、useContextを使って、コンポーネントからモード切り替えをします。

まずはContextを用意

// themeContext.ts

import { createContext, useContext } from 'react';

interface ThemeContextType {
  colorMode: ColorMode;
  setColorMode: () => void;
}

const defaultContext: ThemeContextType = {
  colorMode: 'light', // 現在のモードを管理
  setColorMode: () => {}, // colorMode書き換え用の関数を渡す
};

export const ThemeContext = createContext<ThemeContextType>(defaultContext);
export const useTheme = () => useContext(ThemeContext);

setColorModecolorMode を書き換えるための関数を渡します。(Providerのvalue propsで)

Theme用意

この辺はなんでもいいです。

import { ColorMode, Theme } from './types';

const lightTheme: Theme = {
  background: '#ffffff',
  color: '#000000',
};

const darkTheme: Theme = {
  background: '#222639',
  color: '#f0f5fa',
};

export function getTheme(colorMode: ColorMode): Theme { // mode受けてテーマ返す
  switch (colorMode) {
    case 'light':
      return lightTheme;
    case 'dark':
      return darkTheme;
    default:
      return lightTheme;
  }
}

Providerの用意

上で定義した、getTheme()を使って、テーマを切り替えます

// ThemeProvider.tsx

import React, { useState } from 'react';
import { ThemeProvider as EmotionProvider } from 'emotion-theming';
import { ThemeContext } from '../themeContext';
import { getTheme } from '../theme';

type ColorMode = 'light' | 'dark';

const ThemeProvider: React.FC = ({ children }) => {
  const [colorMode, setColorMode] = useState<ColorMode>('light');

  function toggleColorMode() { // colorMode切り替え用関数
    setColorMode(colorMode === 'light' ? 'dark' : 'light');
  }
  return (
    <EmotionProvider theme={getTheme(colorMode)}>
      <ThemeContext.Provider
        value={{
          colorMode,
          setColorMode: toggleColorMode,
        }}
      >
        {children}
      </ThemeContext.Provider>
    </EmotionProvider>
  );
};

export default ThemeProvider;

あとは、コンポーネントから、useTheme() を使ってテーマを切り替えるだけ!

// App.tsx

import React from 'react';
import './App.css';
import { useTheme } from './themeContext';
import styled from './components/styled';

const App: React.FC = () => {
  const { colorMode, setColorMode } = useTheme();
  return (
    <Container>
      <p>current color mode: {colorMode}</p>
      <button onClick={setColorMode}>toggle color mode</button>
    </Container>
  );
};

export default App;

const Container = styled.div`
  height: 100%;
  background: ${props => props.theme.background};
  color: ${props => props.theme.color};
`;

Typescriptを使っていると、emotionからimportしたstyled を使うとtheme内にProviderで渡したプロパティの型情報が含まれていないため、使えないです。

styledに型情報をもたせて、それを替わりに使います。

参考:https://emotion.sh/docs/typescript#define-a-theme

// styled.tsx

import styled, { CreateStyled } from '@emotion/styled';
import { Theme } from '../types';

export default styled as CreateStyled<Theme>; // 今回のthemeの型

やったね!

やったね。

f:id:morimo7:20190713154542g:plain

おわり

以上です。Githubにあげてあります。

github.com