今回はコンテキストについて学習していきます。
Reactにおけるコンテキスト(Context)とは?
- Reactでは、コンポーネントが親→子、 子→孫 へとPropsを使ってデータを渡すのが基本の流れになっている
- アプリが大きくなると複数のコンポーネント間でデータのやりとりが生じるため、Propsを使ってバケツリレーで渡すのが効率悪いし、コードも汚い
- そこでコンテキストを使うことで、Propsを使わずに、コンポーネント全体にデータを渡すことが可能!
コンテキストの基本
- データをグローバルに使いたい時に使用する
- 具体使用例
- ライトモード / ダークモード
- 認証情報
- ユーザー情報
- etc
コンテキスト使用の手順
- コンテキストの作成
React.createContext
を使ってコンテキスト作成
- コンテキストプロバイダで値を提供
- コンテキストを使用するコンポーネントツリーの上位に、Context.Providerを配置、値を提供
- コンテキストを利用
- コンテキストの値を使いたいコンポーネントで、
useContext
フックで値を取得
- コンテキストの値を使いたいコンポーネントで、
【ログイン状態の実装例】基本的な使い方
コンテキストの作成 と コンテキストプロバイダで値を提供
React.createContext
を使ってコンテキスト作成- コンテキストを使用するコンポーネントツリーの上位に、Context.Providerを配置、値を提供
src/js/contexts/AuthContext.tsx
type AuthContextType = {
isLoggedIn: boolean;
};
export const AuthContext = createContext<AuthContextType>({
isLoggedIn: true,
});
export const AuthProvider = ({ children }: PropsWithChildren) => {
const isLoggedIn = true;
return <AuthContext.Provider value={{ isLoggedIn }}>{children}</AuthContext.Provider>;
};
コンテコンテキストを利用するコンポーネントをchildrenに配置する
src/js/index.tsx
<AuthProvider>
<App />
</AuthProvider>
コンテコンテキストを利用
- コンテキストの値を使いたいコンポーネントで、
useContext
フックで値を取得
src/js/App.tsx
export const App = () => {
const { isLoggedIn } = useContext(AuthContext);
return <div>{isLoggedIn ? "ログイン" : "ログアウト"}</div>;
};
【ログイン状態の実装例】Propsからコンテキストを操作する
コンテキストの作成 と コンテキストプロバイダで値を提供
isLoggedIn
をuseState
で管理
src/js/contexts/AuthContext.tsx
type AuthContextType = {
isLoggedIn: boolean;
setIsLoggedIn: React.Dispatch<React.SetStateAction<boolean>>;
};
export const AuthContext = createContext<AuthContextType>({
isLoggedIn: true,
setIsLoggedIn: () => {},
});
export const AuthProvider = ({ children }: PropsWithChildren) => {
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
return (
<AuthContext.Provider value={{ isLoggedIn: isLoggedIn, setIsLoggedIn: setIsLoggedIn }}>
{children}
</AuthContext.Provider>
);
};
コンテコンテキストを利用
- Propsからコンテキストを操作する
src/js/App.tsx
export const App = () => {
const { isLoggedIn, setIsLoggedIn } = useContext(AuthContext);
return (
<label className="flex gap-2 p-2 border w-fit">
<input
type="checkbox"
checked={isLoggedIn}
onClick={() => setIsLoggedIn(!isLoggedIn)}
/>
<span>{isLoggedIn ? "ログイン" : "ログアウト"}</span>
</label>
);
};
【ログイン状態の実装例】ログアウト中はログイン画面を表示する
本来はサーバーサイドのログインAPIなどがあればいいが、今回は名前が入力されればログイン状態とする。
Context(グローバルに使えるデータ) で、userNameを管理する
userName
・・・ ログインユーザーを保持する情報setUserName
・・・ ログインユーザーをuseState
で管理する
src/js/contexts/AuthContext.tsx
type AuthContextType = {
isLoggedIn: boolean;
setIsLoggedIn: React.Dispatch<React.SetStateAction<boolean>>;
userName: string;
setUserName: React.Dispatch<React.SetStateAction<string>>;
};
export const AuthContext = createContext<AuthContextType>({
isLoggedIn: false,
setIsLoggedIn: () => {},
userName: "",
setUserName: () => {},
});
export const AuthProvider = ({ children }: PropsWithChildren) => {
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
const [userName, setUserName] = useState<string>("");
return (
<AuthContext.Provider
value={{
isLoggedIn: isLoggedIn,
setIsLoggedIn: setIsLoggedIn,
userName: userName,
setUserName: setUserName,
}}
>
{children}
</AuthContext.Provider>
);
};
ログイン状態の有無(isLoggedIn
)で、表示画面を切り替える
src/js/App.tsx
export const App = () => {
// useContextを使って、AuthContextの値を取得
const { isLoggedIn, setIsLoggedIn, userName, setUserName } = useContext(AuthContext);
// isLoggedInの有無でログイン状態を切り替える
if (!isLoggedIn) {
return (
<main>
<h1>ログイン</h1>
<TextField
label="ユーザー名"
type="text"
value={userName} // userNameの値を表示
onChange={setUserName} // userNameの値を変更するためのsetUserNameを呼び出す
></TextField>
{/* ログインボタンクリックで、ログイン状態(isLogdin: true)へ切り替える */}
<ButtonRegister onClick={() => setIsLoggedIn(true)}>ログイン</ButtonRegister>
</main>
);
}
// isLoggedInの有無でログイン状態を切り替える
return (
<main>
<h1>TODOリスト</h1>
<p>ログインユーザー:{userName}</p>
{/* ログアウトボタンクリックで、ログアウト状態(isLogdin: false)へ切り替える */}
<ButtonRegister onClick={() => setIsLoggedIn(false)}>ログアウト</ButtonRegister>
{/* TODOアプリのUI */}
</main>
);
};
setIsLoggedIn を切り替えているだけなので、画面の情報が残るのを解消する
別ファイルでuseContext(AuthContext)
を呼び出し、AuthContext
経由でログイン状態 と ログアウト状態 を定義する
src/js/contexts/use-auth.ts
export const useAuth = () => {
// AuthContextから取得した値を返す
const { isLoggedIn, setIsLoggedIn, userName, setUserName } = useContext(AuthContext);
// ログイン状態を返す
const login = () => {
// ユーザー名が入力されている場合、ログイン状態に切り替える
if (!userName) {
alert("ユーザー名を入力してください");
return;
}
setIsLoggedIn(true);
};
// ログアウト状態を返す
const logout = () => {
// ログアウトされたら、ユーザー名を空にしてログアウト状態に切り替える
setIsLoggedIn(false), setUserName("");
};
return { isLoggedIn, userName, setUserName, login, logout };
};
Appで、ログインボタンとログアウトボタンがクリックされたときに、use-auth.ts で定義した、login
と logout
を実行する
TSX
export const App = () => {
// const { isLoggedIn, setIsLoggedIn, userName, setUserName } = useContext(AuthContext);
const { isLoggedIn, login, logout, userName, setUserName } = useAuth();
// isLoggedInの有無でログイン状態を切り替える
if (!isLoggedIn) {
return (
<main className="flex flex-col mx-auto p-4 gap-5">
<Heading label={"h1"}>ログイン</Heading>
<TextField
label="ユーザー名"
type="text"
value={userName}
onChange={setUserName}
></TextField>
{/* ログインボタンクリックで、ログイン状態(isLogdin: true)へ切り替える */}
<ButtonRegister onClick={login}>ログイン</ButtonRegister>
</main>
);
}
// isLoggedInの有無でログイン状態を切り替える
return (
<main className="flex flex-col mx-auto p-4 gap-5">
<Heading label={"h1"}>TODOリスト</Heading>
<p>ログインユーザー:{userName}</p>
{/* ログアウトボタンクリックで、ログアウト状態(isLogdin: false)へ切り替える */}
<ButtonRegister className="bg-red-500" onClick={logout}>
ログアウト
</ButtonRegister>
</main>
);
};
ログインユーザとTODOの担当者が一致した場合には、ハイライトする処理を追加する
TSX
export const TodoItem = ({ id, task, person, deadline, handleDelete }: Props) => {
// useAuth()から、userNameを使えるようにする
const { userName } = useAuth();
{
// ログインユーザー(userName) と {person} が一致した場合にハイライト
const style = userName === person ? "bg-red-100" : "";
return (
<li className={`py-4 border border-b-gray-100 ${style}`}>
<p>タスク:{task}</p>
<p>担当者:{person}</p>
<p>締切:{deadline}</p>
<ButtonDelete
onClick={() => {
handleDelete(id);
}}
>
削除
</ButtonDelete>
</li>
);
};
ローカルストレージに保存する
src/js/contexts/use-auth.ts
const USER_NAME_KEY = "user-name";
export const useAuth = () => {
// AuthContextから取得した値を返す
const { isLoggedIn, setIsLoggedIn, userName, setUserName } = useContext(AuthContext);
// ログイン状態を返す
const login = () => {
// ユーザー名が入力されている場合、ログイン状態に切り替える
if (!userName) {
alert("ユーザー名を入力してください");
return;
}
setIsLoggedIn(true);
localStorage.setItem(USER_NAME_KEY, userName);
};
// ログアウト状態を返す
const logout = () => {
// ログアウトされたら、ユーザー名を空にしてログアウト状態に切り替える
setIsLoggedIn(false);
setUserName("");
localStorage.removeItem(USER_NAME_KEY);
};
// マウント時にローカルストレージからユーザー名が取得できた場合には、ログイン中として処理する
useEffect(() => {
const userNameData = localStorage.getItem(USER_NAME_KEY);
// ローカルストレージの値が取得できた場合
if (userNameData) {
// ログイン中として扱う処理
setUserName(userNameData);
setIsLoggedIn(true);
}
}, []);
return { isLoggedIn, userName, setUserName, login, logout };
};
ディレクトリ構成を修正
dev-react-practice
└ js
├ components
│ ├ parts
│ │ └ 〇〇.tsx
│ └ todo
│ └ 〇〇.tsx
├ contexts
│ └ 〇〇.tsx
├ hooks
│ └ 〇〇.tsx
├ pages
│ └ 〇〇.tsx
├ types
│ └ 〇〇.tsx
├ App.tsx
├ index.tsx
└
公式サイトのおすすめ構成もみてみる
開発リポジトリ
GitHub – yuta0824/dev-react-practice at add-context-day34
デイトラWeb開発コース フロントエンド開発編 CSSでのスタイリングの練習課題です. Contribute to yuta0824/dev-react-practice development by creating an account on GitHub.
まとめ
コンテキスト(Context)の概念について学習してきました。
- Propsでバケツリレーすることなく、サイト全体でグローバルにデータを管理することができる
感想
コンテキスト(Context)の応用で様々な機能を追加しました。
色々な機能を追加したけど、使っているのはこれまでの学習内容なので、基本の理解が改めて重要に思いました。
- Context 経由で、カスタムフック (Custom Hook)を作る
- ログイン / ログアウト状態を管理する
- useEffect → 副作用(データ取得、DOM 操作)を実行する
- useState → 状態管理
- ブラウザに保存されたデータを使う
- localstorage
Custom Hook
/ useEffect
/ useState
/ localstorage
この辺を曖昧にしておくと、応用で詰まるので基本の理解を深めつつす進めていきたいです。