React の Functional Component から Context API を使う
概要
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