Reactコンポネートーー制御か非制御か
こんにちは。mediba でテクノロジー2G にてFEをしております、楊です。
最近のPJ開発中にあったことで、Reactコンポネートの制御か非制御か当時綺麗に解決できなかったので、 振り返って、色々調べた上にまとめたメモです。
コンポネートの制御と非制御
下手な絵で簡単に説明すると↓
ではどうすれば、非制御であり制御コンポネートでもあるコンポネートを作れるでしょう
最も簡単なやり方ーー内外のStateを持ちながら同期させる
まずは子コンポネート内でStateを持たせて、どんな状態(制御モード・非制御モード)でも自分のStateを使うようにする。 次は制御モードにおいて、内部StateをPropsと同期させれば、問題なさそう!
const Input: FC<{
value?: string;
onChange?: (value: string) => void;
}> = (props) => {
const isControlled = props.value !== undefined;
const [value, setValue] = useState(props.value);
const handleOnChange = (e) => {
if (isControlled) {
setValue(e.target.value);
props.onChange(e.target.value);
}
}
useEffect(() => {
if (isControlled) {
setValue(props.value);
}
},[props.value]);
return (
<input value={value} onChange={handleOnChange}/>
);
};
よくみてみると、制御モードでは気になるところも出てきたね
- 子コンポネート内のState更新はParentより遅い
パフォマンス的によくない、useEffect内でのsetStateの使いなので、余計な再描画が発生してしまう
解決できそうかな、試してみよう
子コンポネート内のState更新はParentより遅い
これだと簡単に解決できそう、制御モードにおいてPropsから渡してきた値そのまま使えばいい。
const finalVal = isControlled ? props.value : value
const handleOnChange = (e) => {
if (isControlled) {
setValue(e.target.value);
props.onChange(e.target.value);
}
}
return (
<input value={finalVal} onChange={handleOnChange}/>
);
こうすれば、同期は一歩遅くても、子コンポネートに使ってもらうStateは必ず最新であることを担保できる。
パフォマンス的によくない、useEffect内でのsetStateの使いなので、余計なレンダリングが発生してしまう
useEffect内でのState同期なので、再描画を防げなくて、簡単なコンポネートであれば、パフォマンスの影響は少ないが、コンプレックスなコンポネートには、問題である
ポイントは同期させるタイミングだね、ならどうしよう、、、、
Stateでvalue保存には、setterで同期させた直後に再描画が始まる、もし再描画が制御可能になったら問題ないでしょう。 保存にはRefを使って、強制再描画には 仮のstateを作って
const [_, setObj] = useState({})
function triggerRendering(){
setObj({})
}
を使う。そうしたら全体は↓
const Input: FC<{
value?: string;
onChange?: (value: string) => void;
}> = (props) => {
const isControlled = props.value !== undefined;
const stateRef = useRef(props.value);
if (isControlled) {
stateRef.current = props.value;
}
const [_, setObj] = useState({});
function triggerRendering() {
setObj({});
}
const handleOnChange = (e: React.ChangeEvent) => {
stateRef.current = e.target.value;
triggerRendering();
props.onChange(e.target.value);
};
return <input value={stateRef.current} onChange={handleOnChange}/>;
};
こうしたことで値の同期による再描画がなくなり、制御同時に非制御のコンポネートが出来上がった。 refとhandleOnChangeの部分を取り出し、hooksにすることで、他のコンポネートに適用することもできるでしょう。
現在medibaではメンバーを大募集しています。
medibaってどんな会社だろうと、興味を持っていただいた方は、カジュアル面談もやっておりますので、お気軽にお申込み頂ければと思います。