2022/07/02
You Might Not Need an Effect
props や state の変化によりコンポーネントを更新したいときに,useEffect を使うべきではない。 以下の二つの場合,useEffect を使うべきではない。
- レンダリングによりデータを変換するとき。
- 例:あるリストを表示前にフィルタリングしたい時。
- リストが変更されたときに,Effect により state を更新したくなるだろうが,これは非効率。
- ユーザーのイベントを処理する時。
外部のシステムと同期するときに Effect が必要となる。
Updating state based on props or state
state を変換して作成される値を,state で管理して,useEffect で更新するべきではない。
悪い例
import { useEffect, useState } from "react"; const App = () => { const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); const [fullName, setFullName] = useState(""); useEffect(() => { setFullName(firstName + " " + lastName); }, [firstName, lastName]); return ( <> <input value={firstName} onChange={(e) => setFirstName(e.target.value)} /> <input value={lastName} onChange={(e) => setLastName(e.target.value)} /> <div>{fullName}</div> </> ); }; export default App;
firstName
とlastName
をつなげたfullName
を state として管理し,両者が変更されるたびに,fullName
を更新している。
また,firstName
もしくはlastName
が変更された時には,fullName
が元の古い値のままレンダリングされ,そこからfullName
が更新され,再レンダリングが走るという点で非効率的。
良い例
import { useState } from "react"; const App = () => { const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); const fullName = firstName + " " + lastName; return ( <> <input value={firstName} onChange={(e) => setFirstName(e.target.value)} /> <input value={lastName} onChange={(e) => setLastName(e.target.value)} /> <div>{fullName}</div> </> ); }; export default App;
fullName
をトップレベルで定数で定義している。firstName
とlastName
の state が変化する度に,再レンダリングが起こりfullName
が再定義される。
Caching expensive calculations
先ほどの例で,email
も state で管理する時を考える。
悪い例
import { useState } from "react"; const App = () => { const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); const [email, setEmail] = useState(""); const fullName = firstName + " " + lastName; return ( <> <input value={email} onChange={(e) => setEmail(e.target.value)} /> <input value={firstName} onChange={(e) => setFirstName(e.target.value)} /> <input value={lastName} onChange={(e) => setLastName(e.target.value)} /> <div>{fullName}</div> </> ); }; export default App;
この時,email
state が変更された時,fullName
の値は変更されていないにも関わらず,もう一度fullName
の計算処理が走ってしまい,非効率的。
良い例
import { useMemo, useState } from "react"; const App = () => { const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); const [email, setEmail] = useState(""); const fullName = useMemo(() => { console.log("fullName rendered!"); return firstName + " " + lastName; }, [firstName, lastName]); return ( <> <input value={email} onChange={(e) => setEmail(e.target.value)} /> <input value={firstName} onChange={(e) => setFirstName(e.target.value)} /> <input value={lastName} onChange={(e) => setLastName(e.target.value)} /> <div>{fullName}</div> </> ); }; export default App;
fullName
を useMemo によりメモ化することで,firstName
とlastName
が変更された時のみfullName
が計算され,email
state が変更されてもfullName
は以前の値がそのまま使われる。