前提
- この記事ではTypeScriptを使用しています。
- 17.0.1 のReactを使用しています。
- Nextjs 10での動作確認をしています。
はじめに
ReactにはReact.FCという型定義があります。 こちらは、React.FunctionComponentが略された型で同じ型です。開発では殆どがReact.FCのほうが使われています。
昔のReactでは、React.SFC型が使われていましたが非推奨になっています。
React.FCとは
React.FCは、constによる型定義でコンポーネントを定義できる型です。コンポーネントというのはReact独自のタグで、オリジナルのタグを作成し、タグの中で他のタグをまとめて定義できるものです。
const MyTag: React.FC = () => { return <div>こんにちは</div>; }; const Container: React.FC = () => { return <MyTag />; };
コンポーネントの中は、returnで返ったタグがレンダリング(描画)されます。constで定義したタグはそのまま他のコンポーネントで呼び出して使う事ができます。
複数タグを描画する
基本的にはreturnで返るタグはひとつだけですが、<></>
で囲むことで、複数タグをレンダリング出来ます。
const MyTag: React.FC = () => { return <> <div>こんにちは</div> <div>こんにちは</div> </>; };
こうすることで、親要素が消えて、子要素のみが直接描画されます。
また、親タグをひとつだけ用意しても同様のことが出来ますが、一階層余分な要素が増えます。CSSなどの定義で、階層によって描画が変わる場合に便利です。
const MyTag: React.FC = () => { return <div> <div>こんにちは</div> <div>こんにちは</div> </div>; };
タグに属性値を渡す
前項では、ただコンポーネントを呼び出しただけなのでコンポーネントの中身を書き換えることができません。コンポーネントに属性値を渡してその内容をレンダリングしてみます。
const MyTag: React.FC<{ title: string; }> = (props) => { return <div>{props.title}</div>; }; const Container: React.FC = () => { return <MyTag title="こんばんは" />; };
MyTagコンポーネントに、titleという属性を付けました。
TypeScriptなので、肩定義にもtitle: string;
がついています。
これはReact.FCがジェネリクス型となっており、引数にどのような属性値が含まれるのか定義することができるようになっています。
const MyTag: React.FC<{ isAm: boolean; t1: string; t2: string; }> = (props) => { return <div>{props.isAm ? props.t1 : props.t2}</div>; }; const Container: React.FC = () => { return <MyTag t1="おはよう" t2="こんにちは" isAm={true}/>; };
属性値には複数の引数を定義することができます。 属性値の型にはstring以外にも様々な型を渡すことが可能で、どんな型でも入れることが出来ます。
文字列の場合は、""で定義しますが、変数やそれ以外のリテラル型やboolean型(true/falseの真偽値)は{}で囲います。
属性値はpropsという変数にオブジェクトとして格納されていて、ドットで呼び出すことができます。Reactでは慣習的にpropsという名前の変数が使われているので、合わせておきます。
この実装の中で下記のような部分があります。
props.isAm ? props.t1 : props.t2
こちらは三項演算子という条件式で、1項がtrue(真)だった場合は、2項を、偽だった場合は三項を返します。 isAmは午前中かどうかと言う意味の真偽値です。
propsを省略する
毎回propsを定義するのは使い勝手が悪いので、直接propsの中の属性値を呼び出せるようにします。
const MyTag: React.FC<{ title: string; }> = ({ title }) => { return <div>{title}</div>; }; const Container: React.FC = () => { return <MyTag title="こんばんは" />; };
propsが { title: string; } に置き換わりました。 こちらは、propsの中にあるtitleを取り出して定義することができる分割代入という方法で、Destructuring assignment 構文と言います。
分割代入は他にも様々なパターンがあります。
const [a, b] = [10, 20]; const { a, b } = { a: 10, b: 20 };
propsを何度も呼び出して使うと、見通しが悪くなるので、分割代入を使うようにすると良いです。
分割代入には別名をつけることも可能です。
const { a: newA, b } = { a: 10, b: 20 };
また定義されていなかった場合、初期値を設定することも可能です。
const { a = 30, b } = { a: 10, b: 20 };
親コンポーネントのpropsをそのまま子コンポーネントに渡す
コンポーネントのレイアウト調整などで複数のコンポーネントに階層化させて実装した場合、コンポーネントを共通化させたい場合があります。
その場合は、...props
と書くことで渡すことが出来ます。
const MyTag: React.FC<{ title: string; }> = ({ title }) => { return <div>{title}</div>; }; const Container: React.FC<{ title: string; }> = (props) => { return <MyTag ...props />; };
分割代入しているとpropsがなくてエラーが出るので、その場合は、下記のようにします。
const MyTag: React.FC<{ title: string; }> = ({ title }) => { return <div>{title}</div>; }; const Container: React.FC<{ title: string; }> = (props) => { const { title } = props; console.log(title); return <MyTag ...props />; };
引数内で分割代入すると使い勝手が悪いので、最初の行で分割代入をする場合もありますが、状況に応じて使い分けると良いです。
子要素を引数として受け渡す
HTMLタグでは他の要素をタグで包むことが出来ます。
<p>子要素</p>
Reactではこの子要素をpropsの中に引数として持ち、コンポーネントの内側で描画することができます。
子要素は、props.children としてアクセスすることが出来ます。
const MyTag: React.FC<PropsWithChildren<{ title: string; }>> = ({ title, children }) => { return <div>{title}: {children}</div>; }; const Container: React.FC = () => { return <MyTag title="こんばんは"><strong>子要素</strong></>; };
PropsWithChildren型は<{...}>
で囲まれている属性値値の他に、childrenを含んだpropsである型を持ちます。
この型を使うことで、childrenを使うコンポーネントであることがわかりやすくなります。
React.VFC
こちらは逆でchildrenを含まないコンポーネントを定義するのに使用します。childrenを使用していないことを明示的に表したい場合は、こちらの型を使用します。
refの転送を行う
Reactコンポーネントでは、属性にrefを使うとタグのDOM要素を取得できます。直接DOMに対して何か処理を行いたい場合はrefを使います。
const ref = useRef(null); const Container: React.FC = () => { return <div ref={ref}>node</div>; };
このrefですが、Reactコンポーネントに対して使用すると、本来refを使用したい要素にうまく引き渡せない場合があります。
const ref = useRef(null); const MyTag: React.FC<{ title: string; ref: any; }> = ({ title }) => { return <div ref={ref}>{title}</div>; }; const Container: React.FC = () => { return <MyTag ref={ref}>node</div>; };
上記の例ですと、refはうまく引き渡せずに、div要素のrefは取得できません。MyTagそのもののrefを取得してしまうからです。
この場合は、forwardRefを使うことで解決できます。
const MyTag = React.forwardRef((props, ref) => ( <div ref={ref}>node</div> ));
fowardRefはその名の通りrefの転送を行ってくれます。 親要素でrefが指定された場合、親要素のrefを取得するのではなく、そのままコンポーネントの引数として渡すことが出来ます。