بیاین در مورد یکی از تکنیکهای ساختن کامپوننت در vue.js صحبت کنیم. HOC یا higher order components. این یک تکنیک یا پترن پیشرفته برای ساختن کاپوننت های قابل توسعه هستش و شاید در بیشتر پروژه ها استفاده نشه اما در کتابخانه ها و third party library ها ممکنه به درد بخوره. به شخصه در پکیج tdata از این روش استفاده کردم. این ایده بیشتر در کامیونیتی ریکت استفاده میشه. لینک داکیومنت .
برای ساخت یک پروژه جدید از vite استفاده میکنیم چون خیلی سریعه. اطلاعات بیشتر در مورد vite . از یکی از دستورات زیر استفاده کنید تا پروژه ساخته بشه.
npm init @vitejs/app myapp cd myapp npm install npm run dev
// or yarn yarn create @vitejs/app myapp cd myapp yarn yarn dev
حالا پروژه ساخته شده و ما دو تا کامپوننت رو میسازیم CounterPlusOne.vue و CounterPlusFive.vue . که در واقع هر دو یک counter ساده رو نمایش میدیم و در CounterPlusOne به عدد یکی اضافه میکنیم و کامپوننت دیگه پنج تا پنج تا اضافه میکنیم. کدا به صورت خواهد برای این دو کامپوننت:
<!-- CounterPlusOne.vue --> <template> <p>counter {{ counter }}</p> <button @click="increment">add</button> </template> export default { data: () => ({ counter: 0, }), methods: { increment() { this.counter += 1; }, }, };
<!-- CounterPlusFive.vue --> <template> <p>counter {{ counter }}</p> <button @click="increment">add</button> </template> export default { data: () => ({ counter: 0, }), methods: { increment() { this.counter += 5; }, }, };
این دو کامپوننت خیلی شبیه هم دیگه هستن و حداقل برای بخش script میتونیم از روش های مختلف استفاده کنیم برای ریفکتور.
تعریف hoc میشه یک فانکشن جاوااسکریپت که یک کامپوننت جدید رو برگشت میده و کامپوننتی که از طریق ارگومان فانکشن دریافت کرده رو رندر میکنه. ایده در اینجاست که بتونیم برای کامپوننت های مختلف منطق مدنظر رو استفاده کنیم. من برای مثال های خودم همیشه از ساده ترین حالت ممکن استفاده میکنم. پس بیایم و فقط یک HOC بسازیم که کمپوننتی بهش پاس داده میشه رو رندر کنه. اسم فایل رو هرچی میخاین بسازین کافیه یک فایل جاوااسکریپت باشه من اسمشو میزارم WithCounter.js :
import { h } from "@vue/runtime-core" function WithCounter(WrappedComponent) { // we will return new component that render's WrappedComponent return { created() { console.log('HOC component created') }, render() { return h(WrappedComponent) } } } export default WithCounter
اگه با ویو کار کرده باشید این سینتکس خیلی اشنا نیست اما حداقل با created اشنا هستیم. واقعیت اینه کامپوننت های ویو چیزی جز Object های جاواسکریپت نیستن به همون شکلی که در کامپوننت ویو خودمون یک object رو export default میکنیم. همون جوری که گفتم HOC یک فانکشن هستش که یک کامپوننت جدید رو برمیگردونه که میشه { ... } return . اما باید این کاپوننت یه چیزی رو برامون نمایش بده توی براوزر یا به اصطلاح رندر کنه. که توی کامپوننت های ویو ما هم از template میتونیم استفاده کنیم هم از متد render. البته خود template هم از render استفاده میکنه و ما لزومی نداره خودمونو درگیر پیچیدگی های این متد کنیم. و منم فرض میکنم شما بلد هستید با این متد کار کنین و برای همین توضیح نمیدم و اگه بلد نیستین مشکلی نداره توی داکیومنت ویو میتونین پیداش کنین. تنها یک فانکشن از هسته ویو لود کردیم که باهاش بتونیم المنت هامونو بسازیم. که با استفاده از این متد میتونیم کامپوننتی که به این متد پاس میدیم در DOM رندرش کنیم همین، فعلا :) برای ثبت یا register کامپوننت ها از طریف HOCی که ساختیم به این شکل خواهد بود(میتونه در بخش script هر کامپوننتی این کارو انجام بدین مثلا App.vue):
import CounterPlusOne from "./components/CounterPlusOne.vue" import CounterPlusFive from "./components/CounterPlusFive.vue" import WithCounter from "./components/WithCounter.js" export default { components: { CounterPlusOne: WithCounter(CounterPlusOne), CounterPlusFive: WithCounter(CounterPlusFive), }, };
الان در کنسول میبینیم که دو بار عبارت HOC component created لاگ شده که این کار توسط WithCounter ما صورت گرفته.
حالا میخایم منطق کامپوننت هامونو به HOC انتقال بدیم. اما باید این HOC که ساختیم برای متد increment به یه شکلی بهش بگیم چن تا چنتا میخایم به عددمون اضافه کنه. خب خیلی راحته میتونیم یک آرگومان به WithCounter ارسال کنیم و چون یک فانکشن هستش هر تعداد ارگومان که خواستیم رو میتونیم ارسال کنیم. اولا در جایی که اونا ثبت کردیم باید ارگومان که مثلا 1 و 5 هست رو ارسال کنیم به HOC و در WithCounter اونو قبولش کنیم، به این شکلی:
// App.vue export default { components: { CounterPlusOne: WithCounter(CounterPlusOne, 1), CounterPlusFive: WithCounter(CounterPlusFive, 5), }, }; // WithCounter.js function WithCounter(WrappedComponent, number)
حالا کافیه منطق اصلی رو ارسال کنیم به WithCounter یا HOC:
function WithCounter(WrappedComponent, number = 1) { return { data: () => ({ counter: 0, }), methods: { increment() { this.counter += number; }, }, render() { return h(WrappedComponent, { counter: this.counter, increment: this.increment }) } } }
در اینجا ما counter و increment رو به شکل prop به کامپوننت فرزند ارسال میکنیم. حالا کامپوننت هامون ریفکتور شده و میتونیم کامپوننت هامونو به این شکلی ساده ترش کنیم :
<!-- CounterPlusOne.vue --> <template> <p>counter {{ counter }}</p> <button @click="increment">add</button> </template> export default { props: { counter: Number, increment: Function, }, };
<!-- CounterPlusFive.vue --> <template> <p>counter {{ counter }}</p> <button @click="increment">add</button> </template> export default { props: { counter: Number, increment: Function, }, };
این مثالی که در این مقاله به کار بردیم شاید خیلی کاربردی نباشه اما اصول استفاده از HOC هارو در ویو به ساده ترین شکل ممکن میبینیم احتمالا یک مثال واقعی از HOC رو در کانال تلگرام vuedotjs نشون بدم و همون جوری که گفته بودم استفاده از این روش عمدتا به library ها محدود میشه. برای همین منم در دوره ویو خودم اینو پوشش ندادم. امیدوارم که این مقاله براتون مفید بوده باشه :))