40
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【React Hooks】useEffectで無限ループを解決する

Last updated at Posted at 2020-05-01

「React Hooks で、1000ミリ秒間隔でランダムな数字を10回描写する」のが目標です。

前回の【async/await版】JavaScriptでループ中にスリープしたい。それも読みやすいコードでという記事で、「1000ミリ秒間隔でランダムな数字を出力する」ことはできたので今回は、「React Hooks で描写する」ことに挑戦しました。
その途中で無限ループに陥ってしまったので、失敗例と成功例をメモしておこうと思います。

失敗例1 useEffectを使わず無限ループに陥る

useEffectを使わず main関数を直で呼び出しているコードです。


import React, { useState } from 'react';
import { StyleSheet, View, Text } from 'react-native';

const StartScreen = () => {
    const [count, setCount] = useState(0);

    function sleep(milliseconds: number) {
        return new Promise((resolve) => setTimeout(resolve, milliseconds));
      }
      
    async function main() {
      for (let i = 0; i < 10; i++) {
          setCount(Math.random())
          await sleep(1000);
      }
    }

    main()
    
    return(
        <View>
            <Text>{count}</Text>
        </View>
    )
}

このコードでは無限ループが起こってしまい、[Unhandled promise rejection: Invariant Violation: Too many re-renders. React limits the number of renders to prevent an infinite loop.] というエラーが出てしまいます。

これは、関数型の React コンポーネントにおいて、内部状態またはプロパティが変更されると、コンポーネントの関数が再実行されるからです。
そのため、

1 main()実行
2 {count}変更
1 main()実行
2 {count}変更
:
:

という無限ループが生じているのです。

失敗例2 useEffectの第二引数になにも設定せず無限ループに陥る

useEffect内でmain関数を実行し、useEffectの第二引数にはなにも設定していないコードです。

import React, { useState, useEffect } from 'react';
import { StyleSheet, View, Text } from 'react-native';

const StartScreen = () => {
    console.log('StartScreen')
    const [count, setCount] = useState(0);

    function sleep(milliseconds: number) {
        return new Promise((resolve) => setTimeout(resolve, milliseconds));
      }
      
    async function main() {
      console.log('main')
      for (let i = 0; i < 10; i++) {
          setCount(Math.random())
          await sleep(1000);
      }
    }
      
    useEffect(() => {
      console.log('useEffect')
      main()
    })
    
    return(
        <View>
            <Text>{count}</Text>
        </View>
    )
}

このコードでも無限ループが生じてしまいます。

これは、関数型の React コンポーネントにおいて、関数の結果が前回の呼び出し時と異なれば、レンダリングが発生するからです。

コンソールの結果を見てみると、以下のような無限ループが生じています。

StartScreen
useEffect
main
StartScreen
useEffect
main
:
:

これは、

1 StartScreen実行
2 コンポーネントが DOM にレンダリングされる
3 useEffectが呼ばれる
4 main()実行で{count}変更
5 もう一度StartScreen実行
6 コンポーネントが DOM にレンダリングされる。
7 useEffectが呼ばれる
8 main()実行で{count}変更
9 さらにStartScreen実行
10 前回の実行時と返却されるJSXの値(count)が異なるため、コンポーネントが DOM にレンダリングされる
11 = 3 useEffectが呼ばれる
12 = 4 main()実行で{count}変更
:
:

という無限ループが生じているためです。

成功例 useEffectの第二引数に空配列を指定して無限ループから脱出

失敗例2のコードに空配列を足しただけのコードです。


import React, { useState, useEffect } from 'react';
import { StyleSheet, View, Text } from 'react-native';

const StartScreen = () => {
    const [count, setCount] = useState(0);

    console.log('StartScreen')

    function sleep(milliseconds: number) {
        return new Promise((resolve) => setTimeout(resolve, milliseconds));
    }
      
    async function main() {
      console.log('main')
      for (let i = 0; i < 5; i++) {
          console.log('main loop')
          setCount(Math.random())
          await sleep(1000);
      }
    }
      
    useEffect(() => {
      console.log('useEffect')
      main()
    },[])
    
    return(
        <View>
            <Text>{count}</Text>
        </View>
    )
}

このコードでは、ランダムな数字が1000ミリ秒間隔で5回表示されます。

これは、useEffectの第二引数に[]を指定すると、マウント時のみ第1引数の関数が実行されるからです。

コンソールの結果は、以下のようになっています。

StartScreen
useEffect
main
main loop
StartScreen
main loop
StartScreen
main loop
StartScreen
main loop
StartScreen
main loop
StartScreen

これは、

1 StartScreen実行
2 コンポーネントが DOM にレンダリングされる
3 useEffectが呼ばれる
4 main()実行
5 {count}が変更される
6 StartScreen実行
7 コンポーネントが DOM にレンダリングされる
5 {count}が変更される
6 StartScreen実行
7 コンポーネントが DOM にレンダリングされる
:
:
5 {count}が変更される
6 StartScreen実行
7 コンポーネントが DOM にレンダリングされる

という処理が実行されていることを意味します。

ちなみに、useEffectの第二引数に[count]を設定すると、useEffectがcount変更時に呼び出されるため無限ループが生じます。

参考記事

関数型Reactコンポーネントでレンダリングと副作用Hookが実行されるタイミング

useEffect完全ガイド

useEffect完全ガイドはまだあまり読めていません。
useEffectの第二引数に[]を指定すると、マウント時のみ第1引数の関数が実行されるのがまだあまり腑に落ちていないのでじっくり読んでいこうと思います。

40
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?