関数コンポーネントのライフサイクルを「マボロシじま」で理解しよう
タイトルから分かる通り「ネタ枠」ですが
「初心者などに説明する時には、こういうエントリの方が理解しやすいかも??🤔」
と思い、書いてみました
「マボロシじま?なにそれ?」って方はゴメンなさい...(ポケモンルビー・サファイアで、たまにしか現れない島のことです)
「マボロシじま」をつくる
ざっとこんな感じで再現してみました
コンポーネント内でステートの変化をつけたかったので、むりやりcnt
というステートを持たせています
import { useEffect, useState } from "react"
export const MaboroshiIsland = () => {
const [cnt, setCnt] = useState(0)
// 「MaboroshiIslandの点滅」と連動
useEffect(() => {
console.log('なっ なんと! きょうは マボロシじま みえるのじゃ!')
// ステートを自動更新する仕組み
const id = setInterval(() => {
setCnt((c) => c + 1)
}, 1000)
return () => {
clearInterval(id)
console.log('きょうは マボロシじま みえんのう・・・・')
}
}, [])
// 「cntの変化」と連動
useEffect(() => {
console.log('現在のカウント: ', cnt)
return () => {
console.log('カウント更新')
}
}, [cnt])
return (
<div>Maboroshi</div>
)
}
ここで押さえておいて欲しいのは2点です
「マボロシじま」を点滅させる
後はこの「マボロシじま」を定期的に出したり、消したりする仕組みをつくりましょう
import { MaboroshiIsland } from './MaboroshiIsland'
const App = () => {
const [island, setIsland] = useState(false)
// 3秒ごとに「点滅」
useEffect(() => {
const id = setInterval(() => {
setIsland(!island)
}, 3000)
return () => clearInterval(id)
})
return (
<div className="App">
{island && (<MaboroshiIsland />)}
</div>
)
}
これを実行してみるとこうなります
この二つは、「useEffectの第二引数、[]
の中に値が指定されているかどうか?」で決まります
[]
が空っぽならば、コンポーネントの内部のステートには関係なく、マボロシじまの点滅時にだけ呼び出され、[]
にステートが指定されていれば、そのステートが変わるごとに呼び出されます
第二引数を指定しないと「全ての変更時に呼び出される」ので、パフォーマンス上良くありません!(後述)
マボロシじまにトレーナーを上陸させてみる
コンポーネントが更新されるのは「内部のステートが切り替わった時だけ」ではありません。
「親から渡されるprops
に変化があった時」にも更新処理が走ります
というわけで「trainer(ポケモントレーナー)」をpropsに定義してみましょう
import { useEffect, useState } from "react"
- export const MaboroshiIsland = () => {
+ export const MaboroshiIsland = ({ trainer }) => {
// 中略
+ // 「trainerの変化」と連動
+ useEffect(() => {
+ if (!trainer) return
+
+ console.log(`トレーナー${trainer}上陸!`)
+
+ return () => {
+ console.log(`トレーナー${trainer}撤退!`)
+ }
+ }, [trainer])
return (
<div>Maboroshi</div>
)
}
trainerの値を途中で変化させる方法として、苦肉の策ですがボタンで切り替えられるようにしてみました
const [island, setIsland] = useState(false)
+ const [trainer, setTrainer] = useState('')
// 中略
+ const toggle = () => {
+ if (!trainer) {
+ setTrainer('satoshi')
+ } else {
+ setTrainer('')
+ }
+ }
// 中略
return (
<div className="App">
+ <button onClick={toggle}>toggle</button>
- {island && (<MaboroshiIsland />)}
+ {island && (<MaboroshiIsland trainer={trainer} />)}
</div>
)
}
実行してみます
途中からボタンを5回連打しましたが、その間**「トレーナーの値に連動する箇所」だけ出力が走っていることがわかります**
useEffectを正しく使わないと何故パフォーマンスに悪いのか?
では最後にアンチパターンを書いてみましょう
MaboroshiIslandに以下のコードを追加します
// 中略
+ // 第二引数指定しない
+ useEffect(() => {
+ console.log('全ての変更で呼び出されるよ!')
+
+ return () => {
+ console.log('こっちも!')
+ }
+ })
+ // 直書き
+ console.log('直書き!')
return (
<div>Maboroshi</div>
)
}
これでさっきの動作をもう一度やってみます
...もはや多すぎて何が何やらですが、このようなことになります
今回のようにconsole.log
で出力するだけならほぼパフォーマンスには差し支えはないですが、これがもしもっと複雑で「重たい処理」だったらどうでしょう?
必要もないのに何度も呼び出され、その度に「重たい処理」が走るので、当然ながら描画速度も落ちます
なので結論としては
おわりに
いかがでしたでしょうか?
useEffectの説明のため、ちょっとお試しにこういうエントリで例えてみました!
こういう記事が役立つかどうかはわかりませんが...、もし気になるところなどございましたらお気軽にメッセージください!
Discussion