LoginSignup
0
0

More than 1 year has passed since last update.

まえがき

第一回に続いて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

です。なんか盛り沢山ですが色違いみたいなもんです。。

参考文献

Reactハンズオンラーニング 第2版 ――Webアプリケーション開発のベストプラクティス

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0