Debouncing คืออะไร ?
Table of contents
Intro
สวัสดีครับ บทความนี้จะพามารู้จักกับ Debouncing โดย Debouncing เป็นเทคนิคที่มีประโยชน์ในการเพิ่มประสิทธิภาพ Performance ของ Client-side applications โดยเป็นหนึ่งเทคนิคในหลายสิ่งที่สำคัญในการพัฒนา Web App และ Software ซึ่งตามปกติโปรแกรมเมอร์จำเป็นต้องเขียนโค้ดโปรแกรมโดยคำนึง Performance เพื่อที่จะเพิ่มประสบการณ์ผู้ใช้งานที่ดี (UX) กับ Software
พื้นฐานเรื่อง Event Listeners
ก่อนที่จะไปดูว่า Debouncing คืออะไรมาทำความเข้าใจเกี่ยวกับ Event listeners ก่อน
element.addEventListener(event, callbackFunction);
ในการพัฒนา Client-side Applications นั้นจะมีเรื่อง Event listeners คือสิ่งที่ขาดไม่ได้สำหรับการ Interacts กับผู้ใช้งานโดยตัวอย่างของ Events เช่น
- การคลิกปุ่ม
- การ Scrolling หน้า
- การกรอกข้อความกับช่อง Input field
- Submit ฟอร์ม และอื่น ๆ
โดย Event listeners จะมี Callback function เพื่อใช้ Triggers เมื่อเกิด Events ต่าง ๆ
จากตัวอย่างด้านล่างนี้จะเห็นว่าทุก ๆ ครั้งที่มี Events ที่ Input จะเกิ ด Requests (API) = จำนวนของ Events
ปัญหา Callbacks จำนวนมากจาก Event listeners
ในบางโอกาส Event listeners เหล่านี้มักจะเกิดขึ้นจำนวนมากทำให้มีปัญหาเรื่องประสิทธิภาพเมื่อถูกเรียก Callbacks ดังนั้นควรมีการควบคุม Callbacks เหล่านี้เท่าที่จำเป็นจึงเป็นที่มาของเทคนิค Debouncing
แก้ปัญหาด้วย Debouncing
แนวคิด Debouncing
Debouncing คือการหน่วงเวลา (Delay) ในระยะหนึ่งก่อนที่จะให้ทำงานใด ๆ โดยมีเงื่อนไขดังนี้
- จะทำงานเมื่อหมดเวลาของ Delay และ User ไม่มีการกระทำใด ๆ กับ Input interface และเกิด Event
- แต่เมื่อ User มีการกระทำใด ๆ กับ Input ก่อนหมดเวลา Delay ค่าของ Delay จะเริ่มต้นใหม่เสมอ
- ค่าของ Delay จะเริ่มต้นใหม่ไปเรื่อย ๆ จนกว่า User ไม่มีการกระทำใด ๆ กับ Input
จากตัวอย่างด้านบนเป็นการใช้ Timeout
เพื่อกำหนด Delay 500 ms
เมื่อเกิด Events ใด ๆ เพื่อให้หน่วงการทำงาน (ไม่ให้ทำงานทันที)
เพราะว่าอาจจะเกิด Events อื่น ๆ ก่อนที่จะให้ทำงานจริง ๆ (เช่น การพิมพ์ตัวอักษรหลาย ๆ ตัวที่พิมพ์ยังไม่เสร็จ) ทำให้การ httpRequest() จะยังไม่ทำงานทันที
จากนั้นเมื่อสิ้นสุด Delay 500 ms
และไม่เกิด Events ใด ๆ อีกโปรแกรมก็จะทำงานตาม Callback ที่กำหนดไว้คือ httpRequest()
ค่า Delay
xxx ms
สามารถกำหนดตัวเลขได้ตามที่ต้องการ ขึ้นอยู่กับบริบทว่าต้องการ Delay เวลานานเท่าไหร่ -> เพื่อจะไม่ input ใด ๆ อีก
อธิบายการทำงานจากรูปภาพ
- Regular คือวิธีแบบปกติที่ไม่ใช้เทคนิคใด ๆ โดยที่ทุก ๆ Input จะเกิด Execute
- Debounce คือวิธีแบบที่ใช้เทคนิค Debouncing โดยที่ทุก ๆ Input จะไม่ทำงานทันทีจนกว่า
จะเลิกทำกระทำกับ Input แล้วครบ Delay
(1500 ms)
// เพิ่มกำหนดตัวแปร Timeout ขึ้นมาตัวนึง
let timeout;
inputField.addEventListener('input', () => {
// เมื่อเกิด Events ของ Input ให้ Reset ค่าใน Timeout
clearTimeout(timeout)
// เมื่อสิ้นสุด Delay 500 ms จะทำงานตาม Callback ที่กำหนดไว้คือ httpRequest()
timeout = setTimeout(httpRequest, 500)
})
Use cases การนำไปใช้
- ใช้ Debouncing กับการ Resize รูปภาพ
- ใช้ Debouncing กับ Event ที่มีคุณสมบัติ Autosave
- เพื่อไม่ต้องการให้เกิดการทำงานอื่น ๆ ในขณะเกิด Drags and drops event
- เพื่อไม่ให้มี Requests (Axios) ใด ๆ จนกระทั้งผู้ใช้งานหยุดกระทำกับ Input
Debouncing with Helper functions
ส่วนนี้คือเทคนิคที่แถมเพิ่มโดยแทนที่จะเขียน Debouncing แบบ Manual ทุกรอบเราสามารถสร้าง Helper functions ที่ช่วยให้ง่ายและสะดวกในการเรียกใช้งานบ่อย ๆ ดังนี้
// helper.js
export function debounce(callback, delay) {
let timeout
return function () {
clearTimeout(timeout)
timeout = setTimeout(callback, delay)
};
}
// ตัวอย่างการใช้งาน
import { debounce } from '/helper.js'
debounce(httpRequest(), 600)
Debouncing with Lodash
ในภาษา JS เราไม่จำเป็นต้อง Implement Debouncing เองให้เสียเวลา โดยเราสามารถใช้ Library ของ Lodash ช่วยดังนี้
import _ from "lodash"
const logHi = () => console.log('Hi')
const debouncedLogHi = _.debounce(logHi, 1500)
debouncedLogHi()
debouncedLogHi()
debouncedLogHi()
// output: Hi
import _ from "lodash"
const logMessage = message => console.log(message)
const debouncedLogMessage = _.debounce(logMessage, 1500)
debouncedLogMessage('first message')
debouncedLogMessage('second message')
debouncedLogMessage('third message')
// output: third message
Debouncing with React
Basically
ตัวอย่างการใช้ Debouncing กับ React โดยพื้นฐาน (สร้างเองแบบไม่ใช้ Libs)
() => { const [requestsCount, setRequestsCount] = useState(0) const [input, setInput] = useState([]) function debounce(callback, delay) { let timeout return (...args) => { clearTimeout(timeout) timeout = setTimeout(() => { callback(...args) }, delay) } } const request = (e) => { // request statement // display text input setInput(prev => [...prev, e.target.value]) // count requests setRequestsCount(prev => ++prev) } const debouncedInputHandler = useMemo(() => debounce(request, 1000), []) return ( <> <p>Input: {JSON.stringify(input, null, 2)}</p> <h3>แก้ปัญหาด้วย Debouncing</h3> <h3>จำนวนการเกิด Requests: {requestsCount}</h3> <input type='text' onInput={debouncedInputHandler} placeholder='Enter text...' /> </> ) }
by Lodash
นี่คือตัวอย่างการใช้ Debouncing ใน React และใช้ Library จาก Lodash
แต่ จะมีการใช้ useMemo
และ useCallback
เข้ามาช่วยป้องกันปัญหาเรื่อง Re-renders ที่ส่งผลต่อประสิทธิภาพ
อ่านเนื้อหาเพิ่มเติมเกี่ยวกับการใช้งาน useMemo และ useCallback อย่างละเอียดได้ที่นี่ "การใช้งานและความแตกต่างระหว่าง useMemo และ useCallback ของ React Hooks"
import _ from "lodash"
const { debounce } = _
() => {
// Request statement
function httpRequest(input) {
// Call API ...
}
// ใช้แบบนี้ก็ได้ แต่จะมีปัญหาเรื่อง Re-renders
//const debouncedInputHandler = debounce(inputHandler, 500)
// แนะนำให้ใช้ useCallback เข้ามาช่วยลดปัญหา Re-renders และให้ประสิทธิภาพที่ดีกว่า
const debouncedInputHandler = useCallback(
debounce(inputHandler, 500), []
)
// หรือจะใช้ useMemo ก็ได้เช่นกัน
/*const debouncedInputHandler = useMemo(() => {
return debounce(changeHandler, 500);
}, []);*/
const inputHandler = (event) => {
const input = event.target.value
httpRequest(input)
}
return (
<>
<input type="text" onInput={debouncedInputHandler}></input>
</>
)
}
สรุป
แนวคิดเทคนิค Debouncing นั้นไม่ได้จำกัดใช้กับเฉพาะ Frontend App ใน Backend App ก็สามารถใช้เทคนิค Debouncing สำหรับประยุกต์การทำ Delay สำหรับ Processing ได้เหมือนกัน
Debouncing เป็นเทคนิคที่เรียบง่ายและเข้าใจง่าย และยังช่วยปรับปรุงประสิทธิภาพอย่างมาก หวังว่าผู้อ่านจะสามารถปฏิบัติและนำแนวคิดนี้ไปใช้ได้ โดยบทความต่อไปจะพูดถึงอีกเทคนิคที่คล้าย ๆ กันคือ Throttling
*อัปเดทเนื้อหา