概要
Vue を使っていて [Vue warn]: You may have an infinite update loop in a component render function.
の警告が出た場合、書き方のどこかがおかしいので直す必要がある。
この記事では、原因とその対処法について書く。
まとめ
- 上記の警告が出るのは、テンプレート(から呼ばれるメソッド)の中で data property の値を変更しているのが原因 (ただし
v-on:xxx="..."
の右辺で値を変更するのは何も問題ない)。
モデルケース
次のような要件の画面を作るとする。
- テキストボックスの入力内容に応じてリアルタイムでバリデーションする
- ユーザー名は最大 11 文字までとする
- バリデーションエラーがあるときのみ、
- テキストボックスの下にエラー表示する
- テキストボックスに
error
という class をつけて枠を赤くする
※あくまでサンプルなので、 API 呼び出しはしない。
これを実現するための実装方法は1つではないが、
仮に以下のような Vue コンポーネントを定義すると、冒頭の infinite update loop
警告が出る。
<template>
<form>
<div>
ユーザー名:
<input type="text" v-model="user_name"
v-bind:class="[ validateMaxLength('user_name', 11) ]"
>
<p class="error" v-if="errors.user_name">{{ errors.user_name }}</p>
</div>
<!-- 以下、このような項目が複数並ぶ -->
</form>
</template>
<script>
export default {
data() {
return {
user_name: '',
errors: {},
};
},
methods: {
/**
* @param {string} name
* @param {number} maxLength 最大文字数
* @return {string} スタイルクラス名
*/
validateMaxLength(name, maxLength) {
const length = Number(this[name].length);
let styleClass;
if (length > maxLength) {
this.$set(this.errors, name, String(maxLength) + '文字以下にしてください');
styleClass = 'error';
} else {
this.$delete(this.errors, name);
styleClass = '';
}
return styleClass;
}
}
};
</script>
<style>
input[type="text"].error {
border-color: red;
}
p.error {
color: red;
}
</style>
原因と解決方法
<input type="text" v-model="user_name"
v-bind:class="[ validateMaxLength('user_name', 11) ]">
のように v-bind:class
の中で validateMaxLength
メソッドを呼び出しているのが原因。
validateMaxLength
メソッドの中では this.$set
や this.$delete
を使って this.errors
の状態を変化させている。
描画処理をしている途中で data property を変更すると無限ループになるおそれがあるため、 You may have an infinite update loop
の警告が出る、ということ。
この警告が出ないようにするには、いくつか修正案がある。
修正案 1
this.errors
の状態を変更する処理を v-on:xxx
に移す。
v-bind:class="..."
の中では this.errors
を読み取るだけにする。
<input type="text" v-model="user_name"
v-bind:class="{ 'error': errors.user_name }"
v-on:input="validateMaxLength('user_name', 11)"
>
修正案 2
this.errors
の状態を変更する処理を watch
に移す。
v-bind:class="..."
の中では this.errors
を読み取るだけにする。
具体的には、
<input type="text" v-model="user_name"
v-bind:class="{ 'error': errors.user_name }"
>
にした上で this.user_name
を watch
して validateMaxLength
を実行すればよい。
watch: {
user_name(newValue) {
this.validateMaxLength('user_name', 11);
}
},
なお、上記のいずれの修正案も validateMaxLength
の戻り値を使っていないため、修正後はメソッドの戻り値が不要になる。
つまり以下でよいということ。
/**
* @param {string} name
* @param {number} maxLength 最大文字数
* @return {void}
*/
validateMaxLength(name, maxLength) {
const length = Number(this[name].length);
if (length > maxLength) {
this.$set(this.errors, name, String(maxLength) + '文字以下にしてください');
} else {
this.$delete(this.errors, name);
}
}