制御コンポーネントとは
React-wayな方法で入力の管理やフォームの値管理を行う。
例)useState()を使ってフォームの値を管理
非制御コンポーネントとは
制御コンポーネントでは無いもの。
例)useRef()を使ってフォームの値を管理
予想
制御コンポーネント(以下CC)では、stateでフォームの値を管理しているため、値が変わるたびにコンポーネントが再レンダリングされる。
一方非制御コンポーネント(以下UC)では、refで値を管理しており、値が変更されたとしてもコンポーネントの再レンダリングは行われない。
そのため、1つのコンポーネントが大量のフォームを持っている場合、CCでは再レンダリングのコストが高くなるため、パフォーマンスが落ちる。
UCでは再レンダリングが行われないため、フォームの数が増えてもパフォーマンスが悪化することはない。
検証
検証のために以下のコードを使いました。公開しているので、ぜひお手元で試してみてください。
import { useEffect, useRef, useState } from "react";
export const Controlled = () => {
// レンダリング時間計測用
const startTimeRef = useRef<number>(0);
const [formData, setFormData] = useState(
Array(50)
.fill("")
.map(() => ({ name: "" }))
);
const handleFormDataChange = (formData: { name: string }[]) => {
startTimeRef.current = performance.now();
setFormData(formData);
};
// フォームとして成立させるために書いた
// パフォーマンス比較としては不要
const handleChange = (
index: number,
e: React.ChangeEvent<HTMLInputElement>
) => {
const { name, value } = e.target;
const updatedFormData = [...formData];
updatedFormData[index] = { ...updatedFormData[index], [name]: value };
handleFormDataChange(updatedFormData);
};
// フォームとして成立させるために書いた
// パフォーマンス比較としては不要
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log(formData);
};
// レンダリングされた時に呼ばれる
useEffect(() => {
console.log("制御コンポーネントがレンダリングされました");
});
// レンダリング時間計測用
useEffect(() => {
if (startTimeRef.current) {
console.log(
`制御コンポーネントのレンダリング時間: ${
(performance.now() - startTimeRef.current) / 1000
}秒`
);
}
startTimeRef.current = 0;
});
return (
<form onSubmit={onSubmit}>
{formData.map((data, index) => (
<div key={index}>
<input
name="name"
value={data.name}
onChange={(e) => handleChange(index, e)}
/>
</div>
))}
<input type="submit" />
</form>
);
};
import { useEffect, useRef } from "react";
export const UnControlled = () => {
const formRef = useRef<HTMLFormElement>(null);
// フォームとして成立させるために書いた
// パフォーマンス比較としては不要
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const inputValue = (formRef.current?.elements[0] as HTMLInputElement).value;
console.log(inputValue);
};
// レンダリングされた時に呼ばれる
useEffect(() => {
console.log("非制御コンポーネントがレンダリングされました");
});
return (
<form ref={formRef} onSubmit={onSubmit}>
{Array.from({ length: 50 }).map((_, index) => (
<div key={index}>
<input type="text" name="name" />
</div>
))}
<input type="submit" />
</form>
);
};
こちらのコードで試してみたところ、CCではフォームの値を変更するたびに、0.01〜0.02秒かかっていることが確認できました。
一方UCでは、フォームの値を変更しても再レンダリングが起きていないことが確認できました。
結論
一定の条件下において、非制御コンポーネントの方がパフォーマンスが良くなるというのは事実であることが確認できました。ですが、1つのコンポーネントあたりのフォーム数が少ない場合、大きな違いはなくなりそうです。
結論、以下のように使い分けるのが良さそうだと感じました。
- 制御コンポーネントを使う
- 入力されている内容に対して、リアルタイムにバリデーションなどの検証を行いたいとき
- 火制御コンポーネントを使う
- 1つのコンポーネントに多数のフォームが存在するとき
参考