LoginSignup
53
31

More than 3 years have passed since last update.

React Hooks と TypeScript で子コンポーネントに state を渡す方法まとめ

Last updated at Posted at 2020-10-23

ReactとTypeScriptを使った子コンポーネントにstateを渡す方法をまとめてみました。

別に"Reactと表記するだけ"でも良いのでは?と思うかもしれませんがコードのサンプルを載せることでTypeScript込みの労力みたいなところを感じ取ってもらえればと思いあえて React x TypeScript として取り上げています。

①ダイレクト
②関数経由
③useContext(createContext)

上記のようにおそらくメジャーな手法の3パターンがあります(他の方法などコメントもらいタイ)。

①ダイレクト

stateをそのまま子に渡す方法です。状態を変更するsetStateでコードの方でサンプル載せました。

stateProps.tsx
import React, { FC, useState } from "react";
import Child from "./Child";

const Parent: FC = () => {
  const [stateProp, setStateProp] = useState<string>("Stateです");

  return (
    <>
      <h1>親です</h1>
      <div>{stateProp}</div>
      <Child setStateProp={setStateProp} />
    </>
  );
};

export default Parent;

あえてそのまま受け取った子コンポーネントを載せました。

stateChildSample.tsx
import React, { FC } from "react";

const Child: FC = ({
  setStateProp
}) => {
  return (
    <>
      <h2>Childです</h2>
      <button onClick={() => setStateProp("更新した")}>Stateを更新する</button>
    </>
  );
};
export default Child;

setStateProp をそのまま使えそうですが当然ながらPropsに型を付けないと以下のように怒られます。

Type '{ setStateProp: Dispatch<SetStateAction<string>>; }' is not assignable to type 'IntrinsicAttributes & { children?: ReactNode; }'.
  Property 'setStateProp' does not exist on type 'IntrinsicAttributes & { children?: ReactNode; }'.ts(2322)

要するに Dispatch< SetStateAction< string > > で型を付けなさいということです。

ちなみに Dispatch と生成された SetStateAction の型はimportしてこないといけません。下のようなコードで更新できるようになりました。

stateChildSample改.tsx
import React, { FC, Dispatch, SetStateAction } from "react";

const Child: FC<{ setStateProp: Dispatch<SetStateAction<string>> }> = ({
  setStateProp
}) => {
  return (
    <>
      <h2>Childです</h2>
      <button onClick={() => setStateProp("更新した")}>Stateを更新する</button>
    </>
  );
};
export default Child;

codesandbox: https://codesandbox.io/s/state-typescript-update-kn8kj?fontsize=14&hidenavigation=1&theme=dark

デメリット

お手軽ですが型付けが少しややこしいと思いました。
またReact公式チュートリアルでもstateはできる限り親の方でキープした方がスッキリかけて単一方向のデータフローの原則を守りやすいと思います。

②関数経由

ポピュラーな方法だと思います。

funcParentSample.tsx
import React, { FC, useState } from "react";
import Child from "./Child";

const Parent: FC = () => {
  const [stateProp, setStateProp] = useState<string>("Stateです");
  const updateState = (): void => setStateProp("更新した");

  return (
    <>
      <h1>親です</h1>
      <div>{stateProp}</div>
      <Child updateState={updateState} />
    </>
  );
};

export default Parent;

型付けも関数の型を渡してあげます。

funcChildSample.tsx
import React, { FC } from "react";

const Child: FC<{ updateState: () => void }> = ({ updateState }) => {
  return (
    <>
      <h2>Childです</h2>
      <button onClick={() => updateState()}>Stateを更新する</button>
    </>
  );
};
export default Child;

codesandbox: https://codesandbox.io/s/func-state-update-b6ohz?fontsize=14&hidenavigation=1&theme=dark

③useContext(createContext)

useContextとはpropsを使わずに子から孫まで値を渡すことができるHooksの一つです。親より以下のグローバル変数といったイメージとも言い換えられます。

親側は "react" ライブラリから { createContext } を引っ張っています。子や孫側では { useContext } を引っ張ります。

親側ではcreateContextを格納した変数をコンポーネントとして使いますが < Context.Provider > といった形で .Provider で値をJSX上に呼び出すことができます。

parent.tsx
import React, { FC, createContext, useState } from "react";
import Child from "./Child";

export type ContextType = string;
export const Context = createContext<ContextType>(""); //ここで初期化

const Parent: FC = () => {
  const [state, setState] = useState(
    "私はContextです。propsで渡してもらっていません。"
  );
  const updateContext = () => setState("Contextを更新したよ。");

  return (
    <>
      <h1>親です</h1>
      <button onClick={updateContext}>contextを更新するボタン</button>
      <Context.Provider value={state}>
        <Child />
      </Context.Provider>
    </>
  );
};

export default Parent;

次は子コンポーネントですが、孫(ChildChild.tsx)、ひ孫(ChildChildChild.tsx)もほぼ同じコードになっており { useContext } をReactライブラリから引っ張ってきています。

加えて import { Context } from "./Parent"; によってcreateContextで初期化した変数を引っ張っており、 { useContext } と一緒に使います。

child.tsx
import React, { FC, useContext } from "react";
import ChildChild from "./ChildChild";
import { Context } from "./Parent";

const Child: FC = () => {
  const ChildContext = useContext(Context);

  return (
    <>
      <h2>Childです</h2>
      <p>{ChildContext}</p>
      <ChildChild />
    </>
  );
};
export default Child;

親側でクリックするとpropsで渡していないですが子にも反映されるようになります。

<button onClick={updateContext}>contextを更新するボタン</button>

codesandbox:https://codesandbox.io/s/props-usecontext-6x1dj?fontsize=14&hidenavigation=1&theme=dark

useContextに関しては以前、5分でわかる useContext の使い方【TypeScriptまで】の記事で上記コードをさらに噛み砕いて詳細に解説しています。

useContextのデメリット

propsリレーの煩わしさや複雑化の解決策になります。一方で子コンポーネントから変更を加えたいケースでは工夫が必要となるため、①や②の手法の方が手っ取り早いです。
子からstateを変更したい場合は【React + Typescript】useContext の値を子コンポーネントから更新が参考になります。

まとめ

基本的には関数かuseContextが無難かと。他のHooksとの兼ね合いや複雑さに応じて渡し方を最適化できていきたいです。

53
31
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
53
31