TL;DR
このページでは、React の useContext フックを Next.js と TypeScript の世界でどうやって使うのかを一緒に見ていきましょう。
| 開発環境 | バージョン | 
|---|---|
| Next.js | 13.4.4 | 
| TypeScript | 5.0.4 | 
| Emotion | 11.11.0 | 
| React | 18.2.0 | 
useContext とは?
useContext とは、React が提供しているフックの一つで、あるコンポーネントから別のコンポーネントへデータを渡すことができます。例えば、あるページでユーザーが入力した情報を、別のページで表示したい場合に使うことができます。
皆さんは遊んだことがあるドミノ倒しを覚えていますか?一つのドミノが倒れると、それが次のドミノを倒し、その次のドミノを倒していく。それぞれのドミノが情報を次のドミノに伝えていくのと似ています。
useContext は、React のツールで、それを使うと一つの情報(データ)を一つのドミノから最後のドミノまで持っていくことができます。これを利用すると、親のドミノから子どものドミノ、孫のドミノへとデータを簡単に送ることができます。
たとえば、あるドミノ(親コンポーネント)が「こんにちは」というメッセージを持っていて、それを一番最後のドミノ(孫コンポーネント)に伝えたいとしましょう。useContext を使わないと、親から子、子から孫へと順番にメッセージを手渡していかなければなりません。
でも、useContext を使えば、そのメッセージを一つの「バケツ」(これが context)に入れて、一番最後のドミノがそのバケツから直接メッセージを取り出すことができます。「こんにちは」のメッセージを一気に最後のドミノまで届けることができるのです。
だから、useContext は特にたくさんのドミノ(コンポーネント)があるときや、一つのメッセージを沢山のドミノに伝えたいときにとても便利なツールなのです。これを使うと、たくさんのドミノの中でも情報をスムーズに伝えることができます。
どのようなときに使うの?
useContext は、アプリケーション全体で共有したいデータが存在するときに使うことが多いです。
例えば以下のような場合に利用します。
ユーザーのログイン情報: あなたが小学生たちに会員制のウェブサイトを作るとしましょう。ログインしたユーザーの情報は、ウェブサイトのいろいろな場所で使うでしょう。ユーザーの名前を表示したり、ユーザーがアクセスできる機能を制御したりします。そのような場合、ログイン情報はアプリケーション全体で共有するのが理にかなっていますよね。
テーマ(デザイン)の設定: あなたがウェブサイトに「ライトモード」と「ダークモード」を実装するとします。ユーザーが選択したモードは、サイト全体の色やスタイルに影響を与えます。これも、アプリケーション全体で共有するデータの一例です。
ローカライゼーション(地域化)設定: ウェブサイトが複数の言語に対応している場合、ユーザーが選択した言語設定をアプリケーション全体で保持する必要があります。
これらのようなケースでは、useContext を使ってデータをグローバルに管理することで、アプリケーション内のどのコンポーネントからでも簡単にそのデータにアクセスできます。
それでは、例を見てみましょう。
import React, { createContext, useContext } from 'react'
type UserData = {
  name: string
  age: number
}
const UserContext = createContext<UserData>({ name: '', age: 0 })
const UserProvider = UserContext.Provider
const UserConsumer = UserContext.Consumer
const DisplayUser = () => {
  const user = useContext(UserContext)
  return (
    <div>
      <p>{`Name: ${user.name}`}</p>
      <p>{`Age: ${user.age}`}</p>
    </div>
  )
}
ここでは useContext を使って、ユーザーの名前と年齢を取得して表示しています。
グローバルな空間
「グローバル」や「共有」といった考え方が、useContext を理解するのにとても役立ちます。
例えば、学校全体が一つの「グローバルな空間」だと考えてみましょう。そして、各クラスはその学校の一部として存在していますね。各クラスはそれぞれ自分たちだけの情報(たとえばクラスの平均点や、クラスのアイドルの名前など)を持っています。でも、学校全体で共有される情報(たとえば給食の献立や、運動会の日程など)もあります。
useContext は、この「学校全体で共有される情報」を管理するためのツールと言えます。どのクラスからでも、これらの共有情報にアクセスすることができます。これが「グローバル」や「共有」という考え方とどうつながるか分かりますか?
どのコンポーネント(クラス)からでも、同じ情報(給食の献立や運動会の日程)にアクセスすることができる。これが useContext の役割と言えます。このツールを使うと、React アプリ全体でデータを共有することができるのです。
Next.js で、useContext を実装
それでは、次に Next.js で useContext を使ってみましょう。まずは、context.ts というファイルを作ります。
// context.ts
import { createContext } from 'react'
export type UserInfo = {
  name: string
  age: number
}
export const UserContext = createContext<UserInfo>({ name: '', age: 0 })
ここでは UserContext というコンテキストを作成し、UserInfo という型を持たせています。この UserContext を使ってデータを渡すことができます。
次に、コンテキストの提供者となる UserProvider コンポーネントを作成します。
// UserProvider.tsx
import { UserContext, UserInfo } from './context'
type Props = {
  userInfo: UserInfo
  children: React.ReactNode
}
const UserProvider = ({ userInfo, children }: Props) => {
  return <UserContext.Provider value={userInfo}>{children}</UserContext.Provider>
}
export default UserProvider
ここで作成した UserProvider コンポーネントは、その子孫コンポーネントに対して UserInfo のデータを提供します。UserProvider は userInfo と children を引数にとり、UserContext.Provider を通じて userInfo の値を子孫コンポーネントに渡します。
useContext を実装
それでは、UserProvider を使ってデータを提供し、そのデータを取得するコンポーネントを作ってみましょう。_app.tsx で UserProvider を使います。
// _app.tsx
import type { AppProps } from 'next/app'
import UserProvider from '../context/UserProvider'
function MyApp({ Component, pageProps }: AppProps) {
  const userInfo = {
    name: 'Taro',
    age: 12,
  }
  return (
    <UserProvider userInfo={userInfo}>
      <Component {...pageProps} />
    </UserProvider>
  )
}
export default MyApp
MyApp コンポーネントで UserProvider を使っています。userInfo として、名前が 'Taro'、年齢が 12 歳というデータを UserProvider に渡しています。
そして、データを表示する UserDisplay コンポーネントを作ります。
// UserDisplay.tsx
import { useContext } from 'react'
import { UserContext } from '../context/context'
const UserDisplay = () => {
  const user = useContext(UserContext)
  return (
    <div>
      <p>{`Name: ${user.name}`}</p>
      <p>{`Age: ${user.age}`}</p>
    </div>
  )
}
export default UserDisplay
UserDisplay コンポーネントでは、useContext フックを使って UserContext からデータを取得し、そのデータを表示しています。
Emotion で実装
最後に、Emotion を使ってスタイルを適用してみましょう。UserDisplay コンポーネントにスタイルを追加します。
// UserDisplay.tsx
import { useContext } from 'react'
import { UserContext } from '../context/context'
import { css } from '@emotion/react'
const textStyle = css`
  font-size: 20px;
  color: blue;
`
const UserDisplay = () => {
  const user = useContext(UserContext)
  return (
    <div>
      <p css={textStyle}>{`Name: ${user.name}`}</p>
      <p css={textStyle}>{`Age: ${user.age}`}</p>
    </div>
  )
}
export default UserDisplay
ここでは、Emotion の css 関数を使ってスタイルを作成し、そのスタイルを p タグに適用しています。こうすることで、テキストの色とサイズを変更できます。
以上が、Next.js と TypeScript で useContext を使った具体的な実装例です。useContext を使うことで、React コンポーネントの木全体でデータを共有できます。コードはシンプルになり、状態管理の複雑さを軽減することができます。これからも Next.js と TypeScript を使った実装を探求していきましょう。