ReactとTypeScriptを使った子コンポーネントにstateを渡す方法をまとめてみました。
別に"Reactと表記するだけ"でも良いのでは?と思うかもしれませんがコードのサンプルを載せることでTypeScript込みの労力みたいなところを感じ取ってもらえればと思いあえて React x TypeScript として取り上げています。
①ダイレクト
②関数経由
③useContext(createContext)
上記のようにおそらくメジャーな手法の3パターンがあります(他の方法などコメントもらいタイ)。
①ダイレクト
stateをそのまま子に渡す方法です。状態を変更するsetStateでコードの方でサンプル載せました。
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;
あえてそのまま受け取った子コンポーネントを載せました。
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してこないといけません。下のようなコードで更新できるようになりました。
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はできる限り親の方でキープした方がスッキリかけて単一方向のデータフローの原則を守りやすいと思います。
②関数経由
ポピュラーな方法だと思います。
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;
型付けも関数の型を渡してあげます。
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上に呼び出すことができます。
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 } と一緒に使います。
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との兼ね合いや複雑さに応じて渡し方を最適化できていきたいです。