React hooksの1つである、useEffect
について学んでいきます。
さて useEffect
とは一体何者なのでしょうか?
useEffectとは
副作用 (effect) フック により、関数コンポーネント内で副作用を実行することができるようになります
参考:副作用フックの利用法
なるほど、useEffectとは 副作用
を実行するためのもののようです。
...副作用
ってなんだろう?
副作用とは
ある機能がプログラム上のデータを変化させ、それ以降の演算の結果に影響を与えることを指します
参考 : 副作用
Reactで言うなら、 レンダリング後、それ以降の演算の結果に影響を与えることを指します
と言えそうです。
でもそれってどういうことなのでしょう。そんなこと、してもよいのでしょうか?
参考 : レンダリングとは
副作用を試す
それ以降の演算の結果に影響を与える
⇦まだよくわからないので試してみましょう。
分かりやすそうな、DOMの書き換え
を行います。
レンダリング後、h1を書き換えてみます。
import { useEffect } from "react"
export const Text = () => {
useEffect(() => {
const elem = document.getElementById('title') as HTMLTitleElement; // 絶対h1がある!
elem.innerText = 'こんばんわ'
})
return (
<div>
<h1 id="title">こんにちわ</h1>
<p>使ってみよう</p>
</div>
)
}
レンダリング時、h1には こんにちわ
というテキストが入っています。
副作用を用いて、h1のテキストを こんばんわ
に変化させることができました。
続いては実験です、副作用をコメントアウトしてみましょう。
同じようにテキストを書き換えてくれるでしょうか?
// useEffect(() => {
const elem = document.getElementById('title') as HTMLTitleElement; // 絶対h1がある!
elem.innerText = 'こんばんわ'
// })
あれ、ページは真っ白で、エラーが起きてしまいました。
Cannot set properties of null (setting 'innerText')
// null のプロパティを設定できません(「innerText」を設定)
id="title"
を見つけることができていません。
つまり、まだレンダリングが終わっていない(DOMのことわかってない)
状態で id="title"
を探しに行ってしまいました。
この実験で、
それ以降の演算の結果に影響を与える
というのは、レンダリングし終わった後に動き出す
ということだったのが実感できました。
useEffectが動くタイミング
レンダリングし終わった後に動き出す
のはわかりました。
では、それって具体的にいつなのでしょう?
useEffectが動くタイミングが3つあります。
- 値の変更時
- 初回レンダリング時
- レンダリング後 // 先ほどの実験です
3は実験すみなので、1からみていきます。
useEffectが動くタイミング[値の変更時]
これはuseStateを使った実験がわかりやすそうです。
2種類のuseStateにて、count
、count2
をそれぞれ用意しました。
export const Counter = () => {
const [count, setCount] = useState<number>(0)
const [count2, setCount2] = useState<number>(0)
useEffect(() => {
console.log(count)
})
useEffect(() => {
console.log(count2)
})
const incrementCount = () => {
setCount(c => c + 1) // *1
}
const incrementCount2 = () => {
setCount2(c => c + 1) // *1
}
return (
<div>
<h1>{count}</h1>
<button onClick={incrementCount}>1増える</button>
<button onClick={incrementCount2}>2増える</button>
</div>
)
}
早速、incrementCount を押してみましょう。
*1
についてはこちらをどうぞ この時のcってなに
1増えるボタン
を押すたびに、console.logが走っているのがわかります!
値の変更時
に動いているのを確認できました。
あれ、全然関係ない console.log(count2)
も動いてしまっていますね。
この場合、useEffectの第二引数に、useEffectの動作を依存させる値
を入れることができます。
useEffect(() => {
console.log(count)
}, [count]) // countに反応して動く
useEffect(() => {
console.log(count2)
}, [count2]) // count2に反応して動く
それぞれ独立して反応させることができました!
useEffectが動くタイミング[初回レンダリング時]
続いて、2,初回レンダリング時 を見ていきます。
やりかたは簡単、第二引数を空配列を指定するだけです。
useEffect(() => {
console.log(count)
}, []) //空っぽ
useEffect(() => {
console.log(count2)
}, [])//空っぽ
まず0が4つ出ましたね。(これについてはこちらをどうぞ)
その後何度クリックしても、console.logが出てくれません。
これは、第二引数を空配列[]にしたことで、
2,初回レンダリング時動く(それ以降動かない)
ということができるのです。
参考:マウント・アンマウント時のみuseEffectを実行する場合
useEffectの無限ループ
useEffectは無限ループにはまってしまった経験はありませんか?
どのような時に起こるのでしょうか。
実装は簡単です。
useEffect(() => {
setCount(c => c + 1) // 追加
console.log(`----${count}----`)
})
useEffectの中に useStateで使用するset部分
を使って関数を走らせただけです。
初回レンダリングでuseEffect走るね、その時のsetCountするね
-> 値の変更されたからもう一度useEffect走るね、その時のsetCountするね
-> 値の変更されたからもう一度useEffect走るね、その時のsetCountするね
:
この状態を避けるためにも、 eslint-plugin-react-hooks を設定しておくのが良いです。
ちなみにこれを修正するには 別のstateなどに依存
するようにします。
useEffect(() => {
setCount(c => c + 1)
}, [userId]) // userIdに変更があった時だけ動いてね
参考:なぜ useEffect では無限ループが起こり得るのか
いったん今回はここまでとします
次回はもう少しuseEffectのことを深く見ていきましょう、
読んでおきたい記事: useEffect完全ガイド