LoginSignup
14
6

More than 3 years have passed since last update.

You may have an infinite update loop in a component render function. 問題の調査

Last updated at Posted at 2020-02-03

tl;dr

  • Vueでconsoleに [Vue warn]: You may have an infinite update loop in a component render function. が表示される
  • render中にdataを変更してしまい、これによって再度renderが呼ばれることが原因
  • Array.prototype.sort() は対象配列を変更する破壊的メソッドなので注意が必要

概要

Vueはモデルが変更されるとリアクティブにビューを変更してくれるので便利なのですが、利用に際して気をつけなければならないこともあります。
その一つが、rendering時の無限ループです。

rendering時の無限ループは次のようなものです。
1. renderが実施される
2. render中にモデル(data)が変更される
3. モデルの変更によって再度renderが実施される
4. render中にモデルが変更される
5. モデルの変更によって再度renderが実施される
...

rendering時に無限ループが発生した場合、次のようなwarningをconsoleに出力して無限ループを止めてくれる機能1がVueにあるため極端に恐れることはないのですが、意図していない動作となるためこのwarningが出ていたら対処するのが吉です。

[Vue warn]: You may have an infinite update loop in a component render function.

found in

---> <App> at src/App.vue
       <Root>

update loopが起きるパターン

templateでdataを変更している

一番かんたんな例です。
template中でcounter++とすることでcounterの値を変更しています。

<template>
  <div id="app">
    <div>{{counter++}}</div>
  </div>
</template>

<script>
export default {
  name: 'app',
  data: () => {
    return {
      counter: 0,
    }
  },
}
</script>

解決方法

template内でdataを変更しないようにします。
基本的に、変更が必要なケースはないはずです。(あったら教えて下さい)

methodでdataを変更している

render中に利用するmethodがdataを変更していても無限ループが起きます。

<template>
  <div id="app">
    <div>{{count()}}</div>
  </div>
</template>

<script>
export default {
  name: 'app',
  data: () => {
    return {
      counter: 0,
    }
  },
  methods: {
    count() {
      this.counter++
      return this.counter
    }
  }
}
</script>

解決方法

データを変更する可能性のあるメソッドが描画で呼ばれるのが間違いなので、メソッドの適切な分割をします。

computedでdataを変更している

<template>
  <div id="app">
    <div>{{count()}}</div>
  </div>
</template>

<script>
export default {
  name: 'app',
  data: () => {
    return {
      counter: 0,
    }
  },
  computed: {
    count() {
      return this.counter++
    }
  }

}
</script>

なお、eslint-plugin-vue を使っている場合はエラーで止めてくれます。

Failed to compile.

./src/App.vue
Module Error (from ./node_modules/eslint-loader/index.js):
error: Unexpected side effect in "count" computed property (vue/no-side-effects-in-computed-properties) at src/App.vue:17:14:
  15 |   computed: {
  16 |     count() {
> 17 |       return this.counter++
     |              ^
  18 |     }
  19 |   }
  20 | 

1 error found.

解決方法

dataに変更を加えないように修正します。

  computed: {
    count() {
      return this.counter + 1
    }
  }

配列dataに破壊的メソッドを使用してしまっている

1番目のパターンと同じです。
配列データを並べ替えて表示するときにArray.prototype.sort()を利用すると思いますが、これは元の配列を変更する破壊的メソッドです。つまり、array.sort() によって並び替えられた新しい配列が返されるのではなくarray自身が並び替えられて返されます。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

sort()の他にはreverse()等も同様。
https://vuejs.org/v2/guide/list.html#Mutation-Methods

<template>
  <div id="app">
    <div v-for="item in array.sort((a, b) => a.value - b.value)" :key="item.name">
        {{item.name}}: {{item.value}}
    </div>
  </div>
</template>

<script>
export default {
  name: 'app',
  data: () => {
    return {
      array: [
        {
          name: 'a',
          value: 20,
        }, {
          name: 'b',
          value: 50,
        }, {
          name: 'c',
          value: 10
        }
      ]
    }
  },
}
</script>

一応表示はされますが、先述のwarningがでます。
スクリーンショット 2020-02-03 11.40.47.png

解決方法

arrayをslice()で新しい配列にコピーした上で、新しい配列を並び替えします。
こうすることでarray自身は変更されなくなります。
[...array].sort()等でも可

<template>
  <div id="app">
    <div v-for="item in array.slice().sort((a, b) => a.value - b.value)" :key="item.name">
        {{item.name}}: {{item.value}}
    </div>
  </div>
</template>

まとめ

JavaScriptの Array.prototype.sort() は元配列をin placeに変更するので注意。


  1. defaultで100回ループしたら停止します 

14
6
1

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
14
6