イベント間引き処理を実装する際に便利な throttle と debounce の使い方を紹介します。
特定のイベントをトリガーとして何かしらの処理をする際に、イベントが連続して発生する場合に負荷軽減のため、指定した時間が経過するまで次のイベント処理が発生しないように制御することを指します。
例えばテキストフォームに入力するとリアルタイムで API 通信して検索結果を表示するような機能を実装した場合、タイピングする度に API 通信が走ると負荷が掛かりすぎる為、イベント間引き処理を入れたりします。
タイトルにある throttle と debounce を使うには lodash をインストールする必要があります。
Lodash (opens new window)
yarn add lodash
Vue.js で使用する場合は前述のインストール後、main.js に以下の記述を追加します。
window._ = require("lodash");
またグローバルでインポートせず、コンポーネントでインポートする場合は以下の記述になります。
<script>
import _ from "lodash";
export default {
components: {},
}
</script>
throttle は連続して繰り返されるイベントを一定感覚で間引きます。
わかりやすい説明があったので引用させて頂きます🙇
連続して大量に繰り返される処理を一定感覚で間引くものです。
よく使われるのは scroll イベントです。スクロールイベントをすべてハンドリングすると処理回数が多くなり、場合によってはスクロールがもっさりしてしまいますよね。それを防ぎます。
以下はテキストフォームの値を監視し、変更があった場合に throttle で 3 秒の間引き処理を入れて、入力値のアラートで表示するサンプルです。
<template>
<div class="throttle">
<input v-model="text" class="input" />
</div>
</template>
<script>
import _ from "lodash";
export default {
data() {
return {
text: ""
};
},
watch: {
text: _.throttle(function(val) {
alert(val);
}, 3000)
}
};
</script>
<style lang="stylus" scoped>
.throttle
.input
width 240px
padding 8px
border-radius 4px
border 1px solid #cfcfcf
box-sizing border-box
&:focus-visible
outline: none
</style>
debounce は連続して大量に繰り返される処理が指定時間内に何度発生しても最後の 1 回だけ実行します。
こちらもとてもわかりやすい説明を引用させて頂きます🙏
連続して大量に繰り返される処理が指定時間内に何度発生しても最後の 1 回だけ実行するものです。 よく使われるのは resize イベントです。「ウインドウサイズが変わったときに要素のサイズを変える」みたいなケースってありますよね。その時、ウインドウの端っこをつまんでぐりぐりとドラッグするイベントを全て取得しても意味がありません。マウスから手を話し resize が完了したときのイベントをとれば十分です。
以下はテキストフォームの値を監視し、変更があった場合に debounce で 3 秒の間引き処理を入れて、入力値のアラートで表示するサンプルです。
前述した throttle との違いが分かるかと思います。
<template>
<div class="debounce">
<input v-model="text" class="input" />
</div>
</template>
<script>
import _ from "lodash";
export default {
data() {
return {
text: ""
};
},
watch: {
text: _.debounce(function(val) {
alert(val);
}, 3000)
}
};
</script>
<style lang="stylus" scoped>
.debounce
.input
width 240px
padding 8px
border-radius 4px
border 1px solid #cfcfcf
box-sizing border-box
&:focus-visible
outline: none
</style>
こちらは debounce を使った実用的(?)なサンプルコードになります。
テキストフォームがタイピングされる度に、検索処理を実行していますが、debounce で 1 秒の間引き処理をしています。
この配列のデータ量で filter するくらいなら、特に間引き処理を入れる必要はないかと思いますが、検索処理が API 通信でデータ取得するような処理だと効果を発揮する実装だと思います。
<template>
<div class="debounce-search">
<div>
<div class="search">
<input
v-model="search"
@keydown="onKeydown"
@keyup="onKeyup"
class="search__input"
/>
<vue-loading
v-if="loading"
type="spin"
color="#9fcdff"
:size="{ width: '24px', height: '24px' }"
class="search__loading"
/>
</div>
<div class="data">
<template v-if="searchList.length">
<ul class="list">
<li v-for="item in searchList" :key="`searchList${item}`">
{{ item }}
</li>
</ul>
</template>
<template v-else>
検索結果がありません。
</template>
</div>
</div>
</div>
</template>
<script>
import _ from "lodash";
import { VueLoading } from "vue-loading-template";
export default {
components: {
VueLoading
},
data() {
return {
search: "",
list: [],
searchList: [],
loading: false
};
},
created() {
this.createList();
},
methods: {
createList() {
for (let i = 1; i <= 20; i++) {
// 7桁のランダムな整数(0埋め)を生成
this.list.push(
("0000000" + (Math.floor(Math.random() * 9999999) + 1)).slice(-7)
);
}
this.searchList = _.cloneDeep(this.list);
},
onKeydown() {
this.loading = true;
},
onKeyup: _.debounce(function() {
const search = this.search;
this.searchList = _.filter(this.list, item => {
return _.includes(item, search);
});
this.loading = false;
}, 1000)
}
};
</script>
<style lang="stylus" scoped>
.debounce-search
.search
display flex
align-items center
&__input
width 240px
padding 8px
border-radius 4px
border 1px solid #cfcfcf
box-sizing border-box
&:focus-visible
outline: none
&__loading
display inline-block
margin-left 8px
.data
padding: 16px 0
.list
margin: 0
</style>