نیما ابرازه
نیما ابرازه
خواندن ۶ دقیقه·۴ سال پیش

چجوری میتونیم یه debounced ref در ویو بسازیم؟

ماجرای نوشتن این پست یکم جالبه. من همیشه دوست داشتم که علاوه بر اینکه پست های بقیه رو میخونم، خودم هم پست بزارم. ولی خب هیچ وقت ایده ای نداشتم که از کجا باید شروع کنم؟ چه موضوعی رو انتخاب کنم؟ و خلاصه سوال ها و موانعی که همیشه باعث می شدند من بیخیال این کار بشم.

تا اینکه که در یک بعد از ظهر بهاری که همینجوری داخل پنل ویرگول می گشتم تا امکاناتش رو ببینم، وارد بخش «نوشته جدید» شدم و یه جمله الکی نوشتم و متوجه شدم که متنی که نوشته میشه همون لحظه ذخیره میشه و نیازی نیست که خودم نوشته رو ذخیره کنم. این شد که سیستم «ذخیره پیش نویس» یا همون «draft» ویرگول خیلی جالب به نظرم رسید و با خودم فکر کردم که ویرگول چجوری میفهمه که چه زمانی و چجوری متنی که نوشتم رو ذخیره میکنه؟

برای پیدا کردن جواب سوالم یکسری سرچ هایی کردم تا به این نتیجه رسیدم که یه مفهومی به نام debounce هست. بعد از اینکه فهمیدم کلیت debounce چیه تصمیم گرفتم یه composition api برای ویو ۳ بنویسم که به راحتی بشه ازش استفاده کرد.


مفهوم Debounce چیه و چه کاربردی داره؟

اول باید بگم که اصلا debounce کردن به چه معناست و کاربرد اصلیش چیه. اگر بخوام خیلی ساده توضیح بدم debounce کردن به این معناست که بیایم یه گروهی از کارها رو که در یک بازه مشخص، پشت سر هم و به صورت متوالی هستند رو فقط یکبار اجرا کنیم:

تفاوت اجرا شدن event ها در حالت عادی و حالتی که از debounce استفاده می کنیم
تفاوت اجرا شدن event ها در حالت عادی و حالتی که از debounce استفاده می کنیم

فایده اصلیش هم زمانیه که ما یه event ای داشته باشیم که تو حالت عادی پشت سر هم و به صورت متوالی و سریع اجرا بشه. برای مثال اسکرول کردن، حرکت موس و تایپ کردن میتونه نمونه خوبی از event هایی باشه که اگر همینجوری ازشون استفاده کنیم بدون وقفه اجرا میشن و خب این اتفاق زمانی که ما بخوایم با هر بار call شدن این event ها یه کار سنگین یا مثلا یه api کال رو انجام بدیم خیلی جالب نیست. چون هم performance رو به شدت کاهش میده و اگر هم بخوایم api کال کنیم، تعداد زیادی ریکوییست الکی به سمت سرور میره در حالی که میشه با debounce کردن، تعداد ریکوییست ها رو کنترل کرد و در نهایت همون نتیجه رو گرفت.

برای اینکه چیزایی که گفتم ملموس تر بشه فکر کردم که یه مثال از دنیای واقعی براتون بزنم. برای همین یه input درست کردم که امکان جستجو داخل یه لیست رو داشته باشه. علاوه بر اون از یک api هم استفاده کردم تا لیست فیلتر شده رو بر اساس مقدار داخل input بگیرم و نمایش بدم. برای اینکه تفاوت استفاده کردن و استفاده نکردن از debounce رو متوجه بشید میتونید دو تا گیف زیر رو ببینید:

زمانی که از debounce استفاده نمی کنیم و به ازای هر حرفی که وارد می کنیم یه ریکوییست به سرور زده میشه :(
زمانی که از debounce استفاده نمی کنیم و به ازای هر حرفی که وارد می کنیم یه ریکوییست به سرور زده میشه :(
زمانی که از debounce استفاده می کنیم همون نتیجه رو با تعداد ریکوییست بهینه تر و کم تر میگیریم :)
زمانی که از debounce استفاده می کنیم همون نتیجه رو با تعداد ریکوییست بهینه تر و کم تر میگیریم :)

یه مثال دیگه هم که اول این نوشته بهش اشاره کردم و میتونه کمک کننده و جذاب باشه، سیستم ذخیره پیش نویس ویرگوله که بر همین اساس کار میکنه.
علاوه بر چیزایی که گفتم اگر داخل گوگل کلمه debounce رو جستجو کنید به یه توضیح خیلی جالب بر می خورید که با وجود اینکه هیچ ارتباطی به بحث ما نداره ولی خیلی خوب میتونه مفهوم debounce رو انتقال بده:

‍‍Electrical contacts in mechanical pushbutton switches often make and break contact several times when the button is first pushed. A debouncing circuit removes the resulting ripple signal, and provides a clean transition at its output.

یه کم ترجمه اش سخته! ولی خلاصش اینه که تماس های الکتریکی زمانی که یک دکمه فشار داده میشه چندین بار مرتبا قطع و وصل میشه. اینجاست که یه جریان debounce شده، اون سیگنالی که فرستاده میشه رو به صورت مرتب و منظم انتقال میده تا جلوی مشکل قطع و وصل شدن پی در پی رو بگیره. این انتقال تمیز و مرتب یه جورایی همون چیزیه که ما از debounce برای هندل کردن event هامون نیاز داریم!

خب میرسیم به بخش پیاده سازی و توضیحات کدها:

تابع اصلی composition مون useDebouncedRef هست که به عنوان یه customRef قراره debouncing رو برای ما هندل کنه.

تابع useDebouncedRef که به عنوان یه customRef قراره debouncing رو برای ما هندل کنه
تابع useDebouncedRef که به عنوان یه customRef قراره debouncing رو برای ما هندل کنه

همونطور که در عکس بالا معلومه، useDebouncedRef یه تابعی هست که دو تا پارامتر به عنوان ورودی میگیره. initialValue مقدار اولیه متغیر reactive ای هست که میخوایم debounce اش کنیم. delay هم همونطور که از اسمش واضحه مدت زمانی (به میلی ثانیه) هست که قراره تاخیر رو برای ما هندل کنه.

در ادامه یه customRef به نام debouncedRef رو داریم که دو تا متد get و set داره که کارشون تقریبا واضحه. وظیفه متد get اینه که مقدار state فعلی رو برگردونه. در متد set هم مقدار state فعلی رو بر اساس مقدار جدید آپدیت میکنیم.

اما از دو تا تابع track و trigger هم استفاده کردیم که مربوط به خود vue میشه و صرفا خیلی خلاصه میگم که کارشون چیه. این دو تا تابع برای این استفاده میشن که vue بتونه property های داخل state مون رو زیر نظر بگیره و با تغییرشون مقادیر جدید رو آپدیت کنه تا بتونه reactivity رو هندل کنه.

در نهایت customRef ای که نوشتیم رو return میکنیم تا بتونیم ازش استفاده کنیم.

تابع debounce که با استفاده از setTimeout زمان اجرای تابع ورودی داده شده رو هندل میکنه
تابع debounce که با استفاده از setTimeout زمان اجرای تابع ورودی داده شده رو هندل میکنه

در ادامه تابع debounce رو داریم که دو تا پارمتر میگیره که اولین پارامتر fn هست که به طور کلی تابعی هست که میخوایم debounce اش کنیم و در بحث فعلی ما fn در واقع همون متد setter از useDebouncedRef مون هست. دومین پارامتر هم delay هست که بالاتر راجع بهش توضیح دادم.

در نهایت این تابع یه arrow function بر میگردونه که میاد ورودی های fn رو spread میکنه (args...) تا بعدا بتونیم fn رو به همراه ورودی هاش صدا بزنیم.

ادامه کدهای این بخش هم واضحه. یه setTimeout داریم که به مدت زمان delay ست میشه و هر وقت delay تموم بشه fn صدا زده میشه تا مقدار جدید state مون داخل useDebounceRef آپدیت بشه. و در نهایت هم باید دو تا نکته مهم رو بگم:

  • شاید از خودتون بپرسید که دلیل وجود if ای که توی تابع debounce هست چیه. دلیلش اینه که اگر timeout ای ست نشده باشه به معنای این هست که setTimeout ای نداریم و تابع باید همون لحظه اجرا بشه.
  • یه چیز خیلی مهم دیگه clearTimeout ای هست که داخل تابع debounce استفاده شده. دلیل استفاده اش هم اینه که اگر قبلا setTimeout صدا زده شده باشه، clear بشه و مشکلی پیش نیاد و همه چیز از اول شروع بشه. در واقع اگر clearTimeout رو نزاریم setTimeout ها همینجوری پشت سر هم صدا زده میشن و عملا اون delay ای که در نظر گرفتیم به درستی اعمال نمیشه!

و در نهایت به کدهای اصلیمون می رسیم تا از useDebouncedRef ای که نوشتیم استفاده کنیم:

استفاده از useDebouncedRef به جای ref معمولی داخل کدهای ویو
استفاده از useDebouncedRef به جای ref معمولی داخل کدهای ویو

برای اینکه بتونیم از useDebouncedRef استفاده کنیم فقط کافیه که مقدار اولیه و delay رو بهش بدیم. حالا دیگه همه چیز آمادست! فقط کافیه با متد watch مقدار query رو زیر نظر بگیریم تا هر موقع تغییر کرد، کاری که میخوایم رو انجام بدیم. (مثلا call کردن یک api یا ...)

برای دیدن کدها میتونید به این ریپازیتوری در گیت هاب مراجعه کنید!

خب دیگه به انتهای این نوشته رسیدیم. امیدوارم چیزایی که گفته شد به دردتون بخوره. شاد و موفق باشید!

vuejsویوvue3
شاید از این پست‌ها خوشتان بیاید