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には以下のように記載されています。
それでは、上記エラーを解消しつつ、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である可能性の型定義をする必要があるのでご注意を!
以上、お読みいただきありがとうございました。
コメント