【React】useContextの使い方 JavaScriptとTypeScriptの違いも

Reactの代表的なhooks、useStateとuseEffectに次ぐ基本のhooksであるuseContext。

そんな基本のhooksでありながら、いまいち理解できなかったため超わかりやすいように、useContextの使い方と、JavaScriptとTypeScriptでの使い方の違いをまとめました。

環境

  • react @18.2.0
  • typescript @4.8.4

useContextの使い方

JavaScriptとTypeScriptを比較する前に、useContextの使い方について改めて整理しておきましょう!

useContextは、バケツリレーをしなくて済むようにしてくれるhooksです。

実際の例をもとに見ていきましょう!

create-react-appでプロジェクトを立ち上げ、当日の日付と名前を表示する以下のような画面を作成する例で見ていきます。

コンポーネントの構成は以下になります。

App → Child → GrandChild

useContextがどう働くのかがわかりやすいようuseContextを使わない場合と使った場合での違いを比較します。

useContextを使わない場合

上から順番に、、Appから見ていきましょう。

import React from "react";
import { Child } from "./Child";

function App() {
  const user = {
    name: "コンテキストさん",
  };
  return (
    <div>
      <Child user={user} />
    </div>
  );
}

export default App;

userという値を持っており、Childコンポーネントに渡しています。

import React from "react";
import { GrandChild } from "./GrandChild";

export const Child = (props) => {
  const { user } = props;
  const today = new Date();

  return (
    <div>
      <p>日付: {today.toLocaleDateString()}</p>
      <GrandChild user={user} />
    </div>
  );
};

Childコンポーネントでは、日付の表示をしており、Appから渡されたuserをさらにGrandChildコンポーネントに渡しています。

import React from "react";

export const GrandChild = (props) => {
  const { user } = props;
  return user !== null ? <p>こんにちは、{user.name}</p> : null;
};

GrandChildでは渡ってきたuserを描画しています。

以上より、Childコンポーネントのuserはただ渡しているだけです。

これが俗に言うバケツリレーです。

このバケツリレーを無くそう!というのがuseContextです。

useContextを使った場合

変更となる点
  • propsがなくなる
  • useContext hookを使用する
  • userの値はコンポーネントでなくProviderが持つ

同じようにAppから順に見ていきましょう。

import React from "react";
import { Child } from "./Child";
import { UserContext } from "./GrandChild";

function App() {
  const user = {
    name: "コンテキストさん",
  };
  return (
    <UserContext.Provider value={user}>
      <Child />
    </UserContext.Provider>
  );
}

export default App;

ChildコンポーネントをContext.Providerで囲います。このProviderはこの先でexportしているので後で出てきます。

import React from "react";
import { GrandChild } from "./GrandChild";

export const Child = () => {
  const today = new Date();

  return (
    <div>
      <p>日付: {today.toLocaleDateString()}</p>
      <GrandChild />
    </div>
  );
};

userのpropsが不要のため無くなりました。

import React, { createContext, useContext } from "react";

export const UserContext = createContext(); //これによりデータ保持がされる

export const GrandChild = () => {
  const user = useContext(UserContext); //useContextがUserContextからデータを取得する
  return user !== null ? <p>こんにちは、{user.name}</p> : null;
};

UserContextというデータを保持するcontextを作成し、useContextを使ってデータを取得しています。

UserContextは、App.jsxでProviderとしても使用されます。

JavaScriptとTypeScriptの違い

useContextの使い方がわかったところで、本題のJavaScriptとTypeScriptでの記述の違いについて見ていきましょう。

GrandChild.jsx内の以下の、Contextを作成している部分について、

export const UserContext = createContext();

この部分をTypeScriptでそのまま使うと、以下のよう忠告を受けます。(エディタはVScodeを使用しています)

(alias) createContext<unknown>(defaultValue: unknown): React.Context<unknown>
import createContext
Expected 1 arguments, but got 0.ts(2554)
index.d.ts(376, 9): An argument for 'defaultValue' was not provided.

参考に、React TypeScript Cheatsheetには以下のように記載されています。

Context | React TypeScript Cheatsheets
Basic example

それでは、上記エラーを解消しつつ、TypeScriptでの書き方を見ていきましょう。

「App.tsx」「Child.tsx」「GrandChild.tsx」のうち、「Child.tsx」は全く差異無しですので省略します。

App
JavaScript
import React from "react";
import { Child } from "./Child";
import { UserContext } from "./GrandChild";

function App() {
  const user = {
    name: "コンテキストさん",
  };
  return (
    <UserContext.Provider value={user}>
      <Child />
    </UserContext.Provider>
  );
}

export default App;

TypeScript
import React from "react";
import { Child } from "./Child";
import { UserContext } from "./GrandChild";

type User = {
  name: string;
};

function App() {
  const user: User = {
    name: "コンテキストさん",
  };
  return (
    <UserContext.Provider value={user}>
      <Child />
    </UserContext.Provider>
  );
}

export default App;

変更箇所は、userの型定義をしている部分のみです。

これは正直useContextとは関係なく、TypeScriptなので型定義が追加になっています。

GrandChild

こちらのコンポーネントの差異がメインです!

JavaScript
import React, { createContext, useContext } from "react";

export const UserContext = createContext();

export const GrandChild = () => {
  const user = useContext(UserContext);
  return user !== null ? <p>こんにちは、{user.name}</p> : null;
};

TypeScript
import React, { createContext, useContext } from "react";

type User = {
  name: string;
};

export const UserContext = createContext<User | null>(null);

export const GrandChild = () => {
  const user = useContext(UserContext);
  return user !== null ? <p>Hello, {user.name}</p> : null;
};

ここで、userがnullの場合とそうでない場合を三項演算子で分岐していますが、もし、この分岐が無く、以下のように記述をすると、

return <p>Hello, {user.name}</p>;
Object is possibly 'null'.ts(2531)

このように警告が出ます。

以下のように?を使った記述をしてもOKです。

return <p>Hello, {user?.name}</p>;

まとめ

useContextの使い方と、TypeScriptでの使用の注意事項についてまとめました。

TypeScriptの場合はnullである可能性の型定義をする必要があるのでご注意を!

以上、お読みいただきありがとうございました。

コメント