まえがき
第一回に続いてReactを掘り下げてみようシリーズです。
第二回は、useContextです
前提
- React 16.8~
- Reactの基本的な説明はしません
- TypeScriptで書いてます
なぜuseContext?
前回のuseState
と比較してみます。
-
useState
- ステートをルートコンポーネントで一元管理するのは限界がある
- propsバケツリレーがとにかく面倒
-
useContext
- propsを経由せずにコンポーネント間でステートを伝達できる
- 工夫次第でコンテキストを気にせずデータを共有できる
とりあえず使ってみる
./src/index.tsx
import React, { createContext } from "react";
import { render } from "react-dom";
import App from "./App";
import colors from "./color-data.json"
interface Color {
title: string
color: string
}
export const ColorContext = createContext([] as Color[])
render(
<ColorContext.Provider value={colors}>
<App />
</ColorContext.Provider>,
document.getElementById("root")
);
./src/App.tsx
import React, { useContext } from "react";
import { ColorContext } from "./index"
export default function App() {
const colors = useContext(ColorContext)
return (
<ul>
{colors.map((color, i) => (
<li key={i}>{color.title}</li>
))}
</ul>
);
}
propsを経由せずにcolors
変数を子コンポーネントに渡せてますね。
ネストが深くても、そのコンポーネントからuseContext
を呼び出せばいいのでpropsバケツリレーからも開放されます。
Store的に使う
個人的にお気に入りの手法ですw
ReduxにStoreという概念がありますが、こっちの方が全然ラクだと思います。
./src/ColorProvider.tsx
import React, { createContext, useContext, useState } from "react";
import colorData from "./color-data.json";
interface IColor {
title: string;
color: string;
}
interface ColorContextType {
colors: IColor[];
addColor: (title: string, color: string) => void;
}
const ColorContext = createContext({} as ColorContextType);
/** ポイント①: コンテキストをカスタムフックとしてエクスポートする */
export const useColors = () => useContext(ColorContext);
const ColorProvider: React.FC = ({ children }) => {
const [colors, setColors] = useState(colorData);
/** ポイント②: ステートの用途を限定して公開する */
const addColor = (title: string, color: string) => {
setColors([ ...colors, { title, color } ]);
};
return (
<ColorContext.Provider value={{ colors, addColor }}>
{children}
</ColorContext.Provider>
);
};
export default ColorProvider;
./src/index.tsx
import React from "react";
import { render } from "react-dom";
import ColorPrivider from "./ColorPrivider";
import App from "./App";
/** Appコンポーネント配下全てでコンテキストが利用できる */
render(
<ColorPrivider>
<App />
</ColorPrivider>,
document.getElementById("root")
);
./src/App.tsx
import React, { useContext } from "react";
import { useColors } from "./ColorProvider"
import FormWithCustomHook from "./FormWithCustomHook"
export default function App() {
/** コンテキストからデータを取得 */
const { colors } = useColors()
return (
<>
<FormWithCustomHook />
<ul>
{colors.map((color, i) => (
<li key={i}>{color.title}</li>
))}
</ul>
</>
);
}
./src/FormWithCustomHook.tsx(前回の例を流用)
import React from "react";
import { useInput } from "./hooks";
import { useColors } from "./ColorPrivider";
export default function FormWithCustomHook() {
const [titleProps, resetTitle] = useInput("");
const [colorProps, resetColor] = useInput("#000000");
/** コンテキストからデータを取得 */
const { addColor } = useColors();
const submit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
addColor(titleProps.value, colorProps.value);
resetTitle();
resetColor();
};
return (
<form onSubmit={submit}>
<input
{...titleProps}
type="text"
placeholder="color title..."
required
/>
<input {...colorProps} type="color" required />
<button>ADD</button>
</form>
);
}
ポイント①: コンテキストをカスタムフックとしてエクスポートする
コード量的には少し減るかなぐらいですが、コンテキストをカスタムフックで隠蔽できることがデカイです。
useState
などのフックと同じ感覚で使用でき、コンテキスト云々を意識する必要がなくなります。
通常
import { useContext } from "react";
import { ColorContext } from "./index"
export default function App() {
const colors = useContext(ColorContext)
// ...
}
カスタムフック
import { useColors } from "./ColorPrivider";
export default function App() {
const { colors } = useColors()
// ...
}
ポイント②: ステートの用途を限定して公開する
これは賛否ありそうですが、ある程度の規模以上のAppであれば考える必要があるかなと。。
setColors
はステートcolors
を更新するための関数なので、公開するとどんな操作も可能になります。
こちらが意図しない操作で、思わぬバグを引き起こす可能性があります。
/** 特定の文字列から始まる色を除外する */
const unintentionalAction() => {
setColors(colors.filter(color => !color.color.startsWith("#ff")));
}
コンテキストは後に出てくるuseReducer
との組み合わせで複雑なステート管理を実現できたりするので、
かなり重要な機能の一つかなと思います。。
次回
コンポーネントを描画->ステートを更新->コンポーネントを再描画->...
というサイクルはもうなんとかなりそうですね。。
じゃあコンポーネントが描画された後に何かしたいときはどうすればいいのか。。
(APIを叩くとか、DOMを書き換えるとか、、)
というわけで次回は、、
- useEffectとuseLayoutEffect
- useMemoとuseCallback
です。なんか盛り沢山ですが色違いみたいなもんです。。