前回、【React】useContexでContextを参照する手順でReact Contextの概要について紹介しました。
実際のアプリケーションでは、useContext
はuseState
・useReducer
・関数などと組み合わせて利用することが多いです。
そこで今回は具体例を交えてuseContextとの組み合わせ方法について紹介します。
なお、ContextはExampleProviderコンポーネントで作成し、以下のようにアプリケーション全体で囲われている前提とします。
src/pages/_app.tsx
import "../../styles/globals.css";
import type { AppProps } from "next/app";
import { ExampleProvider } from "src/components/shared/ExampleProvider";
function MyApp({ Component, pageProps }: AppProps) {
return (
<ExampleProvider>
<Component {...pageProps} />
</ExampleProvider>
);
}
export default MyApp;
目次
useStateと組み合わせるケース
Contextの作成方法
クリックでカウントアップするカウンタアプリケーションは以下のように実装できます。
src/components/shared/ExampleProvider.tsx
import React, { createContext, FC, ReactNode, useState } from "react";
type Props = {
children: ReactNode;
}
type ContextType = {
setCount: (value: number) => void;
count: number;
}
export const ExampleContext = createContext<ContextType>({} as ContextType);
export const ExampleProvider: FC<Props> = ({children}) => {
const [count, setCount] = useState<number>(0);
return (
<ExampleContext.Provider value={{count, setCount}}>
{children}
</ExampleContext.Provider>
)
};
上記のように、useState
で取得したStateおよびState更新メソッドをvalueにセットすることでContextとuseState
を組み合わせられます。
Contextの利用方法
setCount
(stateを更新する関数)をContextから受け取り、setCount
の処理をコンポーネントに実装することでStateを更新できます。
src/pages/index.tsx
import type { NextPage } from "next";
import { ExampleContext } from "src/components/shared/ExampleProvider";
import { useContext } from "react";
import styles from "../../styles/Home.module.css";
const Home: NextPage = () => {
const { count, setCount } = useContext(ExampleContext);
return (
<div className={styles.main}>
<div>{count}</div>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
};
export default Home;
なおsetCount(count + 1)
をsetCount((prevCount) => prevCount + 1)
と実装する場合、setCount
の型はsetCount: (value: React.SetStateAction<number>) => void
とします。
setCount((prevCount) => prevCount + 1)
に関する説明は【React】そろそろ技術ブログで setCount(count + 1) と書くのはやめませんかを参考にしてください。
関数と組み合わせるケース
Providerで定義した関数をContext経由で他のコンポーネントに渡すこともできます。
Contextの作成方法
以下はincrement
というsetCount(count + 1)
を実行する関数とContextを組み合わせた例です。
src/components/shared/ExampleProvider.tsx
import React, { createContext, FC, ReactNode, useState } from "react";
type Props = {
children: ReactNode;
};
type ContextType = {
increment: () => void;
count: number;
};
export const ExampleContext = createContext<ContextType>({} as ContextType);
export const ExampleProvider: FC<Props> = ({ children }) => {
const [count, setCount] = useState<number>(0);
const increment = () => {
setCount(count + 1);
};
return (
<ExampleContext.Provider value={{ count, increment }}>
{children}
</ExampleContext.Provider>
);
};
Contextの利用方法
Contextから受けとった関数を実行することでStateが更新されます。
src/pages/index.tsx
import type { NextPage } from "next";
import { ExampleContext } from "src/components/shared/ExampleProvider";
import { useContext } from "react";
import styles from "../../styles/Home.module.css";
const Home: NextPage = () => {
const { count, increment } = useContext(ExampleContext);
return (
<div className={styles.main}>
<div>{count}</div>
<button onClick={increment}>+</button>
</div>
);
};
export default Home;
先ほど紹介した「useStateと組み合わせるケース」ではsetCount
の実装をContextを利用するコンポーネントで実装する必要がありました。
しかし関数とContextを組み合わせることにより、Contextを利用するコンポーネントはStateの更新ロジックを気にする必要がなくなります。
useReducerと組み合わせるケース
useReducer
は、より複雑なStateを管理したい場合に利用するuseState
の代替となるHooksです。
useReducer
はReducer関数((state, action) => newStateという型をもつ関数)を受け取り、StateとDispatch関数のペアを返します。
Contextと組み合わせることで、StateとDispatch関数がグローバルで扱えます。
その結果、Reduxのように「各コンポーネントでActionを実行し、グローバルなStateを取得する」というアプリケーションが実現できます。
Contextの作成方法
カウンタの値をStateで管理し、値を変更する処理をDispath関数で定義した例について考えます。
import React, { createContext, FC, ReactNode, useReducer } from "react";
//========
// Action
//========
const ActionTypes = {
increment: "increment",
decrement: "decrement",
} as const;
type ActionTypes = typeof ActionTypes[keyof typeof ActionTypes];
type IncrementActionType = {
type: "increment";
};
type DecrementActionType = {
type: "decrement";
};
type CounterActionTypes = IncrementActionType | DecrementActionType;
//========
// State
//========
type StateType = {
count: number;
};
const initialState: StateType = {
count: 0,
};
//========
// Reducer
//========
const reducer = (state: StateType, action: CounterActionTypes): StateType => {
switch (action.type) {
case ActionTypes.increment: {
return { count: state.count + 1 };
}
case ActionTypes.decrement: {
return { count: state.count - 1 };
}
default:
throw new Error();
}
};
//========
// Context
//========
type ContextType = {
state: StateType;
dispatch: React.Dispatch<CounterActionTypes>;
};
export const ExampleContext = createContext<ContextType>({} as ContextType);
//========
// Provider
//========
type Props = {
children: ReactNode;
};
export const ExampleProvider: FC<Props> = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<ExampleContext.Provider value={{ state, dispatch }}>
{children}
</ExampleContext.Provider>
);
};
上記のように、useReducer
で取得したStateとDispatch関数をvalueにセットすることでContextとuseReducer
を組み合わせられます。
Contextの型(ここでいうContextType
)のプロパティにはStateとDispatch関数を定義します。
Contextの利用方法
受け取ったDispatch関数を利用してActionを実行することでStateが更新されます。
import type { NextPage } from "next";
import { ExampleContext } from "src/components/shared/ExampleProvider";
import { useContext } from "react";
import styles from "../../styles/Home.module.css";
const Home: NextPage = () => {
const { state, dispatch } = useContext(ExampleContext);
return (
<div className={styles.main}>
<div>{state.count}</div>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
</div>
);
};
export default Home;
さいごに
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!