🦁

React の Functional Component から Context API を使う

2021/02/08に公開

概要

React の Context(コンテキスト) API はコンポーネント間でデータを共有する方法の一つです。 Props として渡す方法と比較すると、コンテキストを使う方法では親から子に直接渡す必要がないので、たくさんのコンポーネントに props を書かないといけない props地獄 を避けることが出来ます。
アプリケーション全体で共有したいデータ(例えば、カラーテーマなど)に対してコンテキストを使えば、簡潔に実装することができます。

Context API については 公式ドキュメント に説明がありますが、サンプルコードが Class Component を使った実装になっています。私は Functional Component を使いたいです。そこで、この記事では Functional Component での Context API を使ったサンプルコードを示そうと思います。

※ 私は主に React Native を使っているので、この記事でも React Native でサンプルコードを書こうと思います。

例題

この記事では、カラーテーマをボタンで切り替える例を扱います。(公式ドキュメントと同じ)
画像のように、ボタンがあって、ボタンを押すとボタンの色が変わるアプリを作ります。

コンテキストを動的に切り替える

まずは、テーマとコンテキストを定義します。ここは Class Component を使う場合と同じです。

theme-context.js

import React from 'react';

export const themes = {
  light: {
    foreground: '#000000',
    background: '#eeeeee',
  },
  dark: {
    foreground: '#ffffff',
    background: '#222222',
  },
};

export const ThemeContext = React.createContext(
  themes.dark
);

次に、ボタンになるコンポーネントを定義します。ここでのポイントはフック関数を使っていることです。Functional Component で実装する場合には、useContext 関数が使えます。この関数はコンテキストの現在の値を返すので、コンポーネントの中からコンテキストの値を参照することができます。
Class Component の場合には、ThemedButton.contextType = ThemeContext;のようにして参照するコンテキストを指定しますが、この1行を書く代わりに useContext を使うイメージです。

themed-text.js

import React, { useContext } from 'react';
import { Text } from 'react-native';
import {ThemeContext} from './theme-context';

function ThemedText(props) {
  const theme = useContext(ThemeContext);
  return (
    <Text
      {...props}
      style={{ backgroundColor: theme.background, color: theme.foreground }}
    />
  )
}

最後に、アプリケーションを定義します。ここでもフック関数を使っていて、現在のテーマを保持するために useState を使っています。現在のテーマが theme に保持されていて、ボタンが押されるたびにこの値が、light -> dark -> light -> ... と変化します。

App.js

import React, { useState } from 'react';
import { StyleSheet, View } from 'react-native';
import { ThemeContext, themes } from './theme-context';
import ThemedText from './themed-button';

export default function App() {
  const [theme, setTheme] = useState(themes.light);
  const toggleTheme = () => {
    setTheme((theme) => theme === themes.light ? themes.dark : themes.light)
  }
  return (
    <View style={styles.container}>
      <ThemeContext.Provider value={theme}>
        <ThemedText onPress={toggleTheme}>
          Toggle Theme
        </ThemedText>
      </ThemeContext.Provider>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

子コンポーネントからテーマを切り替える

上の例では、親コンポーネント(App.js)でテーマを切り替えました。子コンポーネント(今回の例では、themed-text.js)からテーマを切り替えることもできます。

まず、ThemeContext の定義にはテーマ切り替え関数の定義を追加します。

theme-context.js (変わった箇所だけ)

export const ThemeContext = React.createContext({
  theme: themes.dark,
  toggleTheme: () => {},
});

次に、子コンポーネントから toggle 関数を取得して onPress 属性に設定します。

themed-text.js

function ThemedText(props) {
  const { theme, toggleTheme } = useContext(ThemeContext);
  return (
    <Text
      {...props}
      style={{ backgroundColor: theme.background, color: theme.foreground }}
      onPress={toggleTheme}
    >
      Toggle Theme
    </Text>
  )
}

最後に、親コンポーネントではコンテキストの値として toggle 関数を渡します。

App.js

export default function App() {
  const [theme, setTheme] = useState(themes.light);
  const toggleTheme = () => {
    setTheme((theme) => theme === themes.light ? themes.dark : themes.light)
  };
  
  return (
    <View style={styles.container}>
      <ThemeContext.Provider value={{ theme, toggleTheme }}>
        <ThemedText />
      </ThemeContext.Provider>
    </View>
  );
}

これで、子コンポーネントからテーマを切り替えられるようになりました。

Discussion