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時の無限ループは次のようなものです。
- renderが実施される
- render中にモデル(data)が変更される
- モデルの変更によって再度renderが実施される
- 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>
解決方法
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に変更するので注意。
-
defaultで100回ループしたら停止します ↩