ReactのContextについて学習した。
Table of contents
Open Table of contents
Contextとは
一般的にContextは特定の状況、状態に関する情報を指す。 「アプリケーションにユーザーがログインしている」「どのユーザーが操作を行なっているか」「使用中の言語」など、がそれに該当する。 また、「ダークモード」などのアプリケーション全体に関わるグローバルな設定も関係する。
ReactにおけるContextは、複数のコンポーネントで共有したい状態や値を管理する仕組みである。 ユーザーのログインの状態や言語設定などの情報は、いろんなコンポーネントで使用する可能性がある。
コンポーネントが情報を取得する場合、Propsを介して情報を渡す必要がある。 しかし、コンポーネントの階層が深くなると、各コンポーネント毎にPropsを渡す記述が増えるため、管理が難しくなる。
そこでContextを使用すると、コンポーネントが直接それらの情報を取得できるようになる。
ReactのContextは、useContextというHookを使用して、コンポーネントがContextの値を取得できるようになる。
useContextの使い方
サンプル
簡単なサンプルを記載する。
TestContext.tsxでcreateContextとuseContextを使用して、Contextの作成と使用に関する設定を行う。
各コンポーネントで使用できるようexportする。
子コンポーネントTestComponent.tsxでは、コンポーネント内でuseTestContextを変数valueに代入する。
valueは任意の位置で{value}として表示する。
親コンポーネントでは、TestContextとTestComponentをimport。
作成したContextを使用して、TestContext.Providerのvalueで値を渡す。Providerは名前の通り、データを配る役目があります。
今回の例では、渡す値はconst valueで定義している。
値を渡したいコンポーネントはTestContext.Providerで囲う。
親コンポーネントApp.tsx
import { TestContext } from './context/TestContext';
import TestComponent from './components/TestComponent';
function App() {
const value = '渡す情報';
return (
<TestContext.Provider value={value}>
<TestComponent />
</TestContext.Provider>
);
}
export default App;
子コンポーネントTestComponent.tsx
import { useTestContext } from '../context/TestContext';
function TestComponent() {
const value = useTestContext();
return (
<div>
<p>{value}</p>
</div>
);
}
export default TestComponent;
コンテキストの設定TestContext.tsx
import { useContext, createContext } from "react";
type TestContextType = string;
export const TestContext = createContext<TestContextType | null>(null);
export function useTestContext() {
const context = useContext(TestContext);
if (context === null) {
throw new Error('useTestContext must be used inside TestContextProvider');
}
return context;
}
ユーザーの追加、一覧
名前と権限を選択し、ユーザー登録を行う機能を作成。 登録後は一覧表示に追加する。
UserContext.tsxでcreateContextとuseContextを使用して、Contextの作成と使用に関する設定を行う。
各コンポーネントで使用できるようexportする。
typeも合わせて定義しておく。Userはユーザーの情報を表す型。他コンポーネントでも使用するので、exportする。
UserProvider.tsxでUserContext.Providerを使用して、Contextの値を渡す。
type UserProviderPropsを定義して、childrenとvalueを受け取る。
valueで受け取るのはusersとaddUser。
usersはinitialUsersで初期値を設定する。
addUserはユーザーを追加する関数。
UserComponent.tsxで表示部分とフォームを作成する。
useUserContextを使用して、Contextの値usersとaddUserを取得する。
usersはmapを使用して、ユーザーの一覧を表示する。
addUserはフォームで入力されたユーザー情報を追加する。
親コンポーネントApp.tsx
import { UserProvider } from './chapters/context/UserProvider';
import { UserComponent } from './chapters/context/UserComponent';
function App() {
return (
<UserProvider>
<UserComponent />
</UserProvider>
);
}
export default App;
コンテキストの設定UserContext.tsx
import { createContext, useContext } from "react";
export type User = {
id: number;
name: string;
role: "admin" | "editor";
}
type UserContextValue = {
users: User[];
addUser: (name: string, role: User["role"]) => void;
};
export const UserContext = createContext<UserContextValue | null>(null);
export function useUserContext() {
const context = useContext(UserContext);
if (context === null) {
throw new Error('useUserContext must be used within UserProvider');
}
return context
}
プロバイダーの設定UserProvider.tsx
import { useState, type ReactNode } from "react";
import { UserContext, type User } from "./UserContext";
type UserProviderProps = {
children: ReactNode;
}
const initialUsers: User[] = [
{id: 1, name:'Taro', role: 'admin'},
{id: 2, name:'Hanako', role: 'editor'},
];
export function UserProvider({ children }: UserProviderProps) {
const [users, setUsers] = useState<User[]>(initialUsers);
const addUser = (name: string, role: User["role"]) => {
setUsers((currentUsers) => [
...currentUsers,
{
id: currentUsers.length + 1,
name,
role,
},
])
}
return (
<UserContext.Provider value={{ users, addUser }}>
{children}
</UserContext.Provider>
);
}
コンポーネントの作成UserComponent.tsx
import { useState } from 'react';
import { useUserContext } from './UserContext';
export function UserComponent() {
const { users, addUser } = useUserContext();
const [newUserName, setNewUserName] = useState('');
const [newUserRole, setNewUserRole] = useState('editor');
return (
<div>
<ul>
{users.map((user) => {
return <li key={user.id}>
{user.name} ({user.role})</li>;
})}
</ul>
<form onSubmit={(e) => {
e.preventDefault();
if (!newUserName.trim()) return;
addUser(newUserName, newUserRole);
setNewUserName('');
setNewUserRole('editor');
}}>
<input
value={newUserName}
onChange={(e) => setNewUserName(e.target.value)}
/>
<select
value={newUserRole}
name="role"
onChange={(e) => setNewUserRole(e.target.value)}
>
<option value="admin">Admin</option>
<option value="editor">Editor</option>
</select>
<button type="submit">Add User</button>
</form>
</div>
);
}
まとめ
Contextは、複数のコンポーネントで共有したい値を管理するための仕組みである。 通常、親から子へ値を渡す場合にはPropsを使用するが、コンポーネントの階層が深くなると、何度もPropsを渡す必要があり管理が煩雑になる。 Contextを使うことで、階層の深いコンポーネントに毎回Propsを渡さなくても、必要なコンポーネントから値を取得できる。
ただし、すべての状態でContextを使用する必要はない。
コンポーネント内だけで完結する状態や値はuseStateを使い、複数コンポーネントで共有したい場合はContextを使う。
状態管理はReactの中でもかなり重要な機能。