یکی از چالش برانگیزترین بخشهای ساخت یک وباپلیکیشن جهانی با استفاده از Vue، این است که دریابید از کدام هوکهای Vue و مسیریاب Vue برای درخواست دادهها در طی lifecycle مسیر (Route Lifecycle) استفاده کنید.
برای این که از عهده این مشکل بر بیاییم، باید یک درک خوب از lifecycle مسیر و زمانی که تمام هوکهای Vue و مسیریاب Vue فراخوانی میشوند، داشته باشیم.
در این مقاله، این موضوعات و نحوه ساخت هوکهای کامپوننت مسیر سفارشی برای درخواست دادهها، به گونهای که برای برنامه شما منطقی باشد را پوشش خواهیم داد.
Lifecycle مسیر (Route Lifecycle)
Lifecycle مسیر با یک درخواست جهتیابی به مسیریاب شروع میشود. مسیریاب یک مسیر مطابق را یافته و کامپوننتهای مسیر ناهمگام را resolve میکند. اگر جهتیابی رد (reject) شود، جهتیابی به مسیر مطابق تایید میشود و کامپوننتهای مسیر رندر میشوند. پس ما در کجای این lifecycle مسیر باید دادهها را درخواست کنیم؟
دادههای خود را دستهبندی کنید
طبق تجربه من، دادههای مربوط به هر مسیر داده شدهای میتوانند به سه دسته تقسیم شوند: Permissions، Critical و Lazy.
Permissions - دادههای مورد نیاز برای تعیین این که آیا یک کاربر به یک مسیر دسترسی دارد یا نه. برای این که تعیین کنیم این دادهها میتوانند وارد مسیر شوند یا نه، باید قبل از این که جهتیابی تایید شود، این دادهها درخواست شده و در دسترس باشند.
Critical - مهمترین دادهها برای تجربه کاربری یک مسیر. این دادهها باید به محض این که جهتیابی تایید شده است، درخواست شوند. این که این دادهها باید برای رندر کردن در دسترس باشند یا نه، به شما و تجربهای که میخواهید کاربرانتان داشته باشند بستگی دارد.
Lazy - دادههای مکمل که نسبت به تجربه کاربری حیاتی نیستند. این دادهها باید بعد از دادههای Critical درخواست شوند، و همچنین نباید رندر کردن را مسدود کنند.
این یک مدل پایه برای زمانی است که دادهها باید در طی lifecycle یک مسیر درخواست شوند.
در واقع... چهار lifecycle مسیر
به نظر میرسد در یک وباپلیکیشن جهانی Vue، هر مسیر داده شدهای میتواند چهار lifecycle مسیریابی مختلف داشته باشد. در اینجا خلاصهای از lifecycleها و تفاوتهای کلیدی آنها مشاهده مینمایید:
درخواست سرور (Server Request)
در نقطه ورود سرور (Server entry point) یک برنامه، یک درخواست مدیریت شده، و از یک lifecycle مسیر میگذرد تا دادهها و HTML مسیر اولیه را برگرداند. در طی این lifecycle، دادههای critical باید قبل از رندر کردن در دسترس باشند و دادههای lazy نباید درخواست شوند؛ زیرا ما نمیخواهیم که این دادهها جلوی فرایند رندر کردن را بگیرند.
راهاندازی کلاینت (Client Initialization)
در نقطه ورود کلاینت (Client entry point) برنامه، یک lifecycle مسیر دیگر به وقوع میپیوند تا برنامه را راهاندازی کند. اجباری نیست که دادههای permission و critical درخواست شوند؛ زیرا از پیش توسط سرور درخواست شدهاند. دادههای lazy میتوانند در هنگامی که برنامه راهاندازی میشود، برای مسیر اولیه درخواست شوند.
جهتیابی کلاینت (Client Navigation)
پس از راهاندازی کلاینت، کاربر به یک سمت مسیر جدید جهتیابی میکند که از کامپوننتهای مختلف استفاده میکند، نه از مسیر فعلی. در این مورد، ما از یک lifecycle مسیر کامل بر روی کلاینتی که دادههای permission، critical و lazy را درخواست میکند، میگذریم. بر خلاف lifecycle درخواست سرور،در این lifecycle به شما بستگی دارد که دادههای critical قبل از رندر کردن کامپوننتهای مسیر در دسترس باشند یا نه.
جهتیابی بروزرسانی کلاینت (Client Update Navigation)
این مورد هم درست به مانند Lifecycle جهتیابی کلاینت، انتظار دارد که کاربر به سمت مسیری جهتیابی کند که از کامپوننتهای مشابه مانند مسیر فعلی (برای مثال /profile/123/ -> /profile/456/) استفاده میکند. در این مورد، یک مجموعه متفاوت از هوکهای بروزرسانی فراخوانی میشوند؛ زیرا ما از قبل به مسیر مورد نظر وارد شدهایم و کامپوننتها ساخته شدهاند.
بیایید مدل خود را بروزرسانی کنیم.
هوکهای lifecycle (Lifecycle Hooks)
حال که یک مدل از زمانی که دادهها درخواست میشوند داریم، بیایید ببینیم که کدام هوکهای lifecycle برای درخواست دادهها در این مکانها مناسبتر هستند. نمودار زیر هوکهای lifecycle که توسط هر کدام از lifecycleهای مسیر فراخوانی میشوند را نمایش میدهد.
چند نکته درباره این نمودار:
- اگر مسیر مطابق رد (reject) نشوند، تایید جهتیابی در داخل هوک onReady اتفاق میافتد.
- عموما beforeEach، beforeResolve و afterEach برای جهتیابی بر روی کلاینت مورد استفاده قرار میگیرند.
- هوکهای lifecycle با نامهای beforeRouteLeave، beforeDestroy و destroyed که در طی lifecycle جهتیابی کاربر فراخوانی میشوند، از روی عمد جا گذاشته میشوند. این هوکها در انتهای عمر مسیر فراخوانی میشوند، که این مسئله باعث میشود انتخاب خوبی برای درخواست دادهها نباشند.
از چه هوکهایی باید استفاده کنیم؟
با داشتن یک تصویر کامل از وقتی که هوکهای lifecycle فراخوانی میشوند، ما میتوانیم ببینیم که چرا برخی هوکهای lifecycle برای درخواست دادهها نسبت به باقی موارد بهتر هستند.
هوکهایی که پس از resolve شدن کامپوننت اتفاق میافتند
منطق درخواست یک هوک باید داخل کامپوننت مسیر قرار داشته باشد، تا کد آن بتواند تقسیم بندی شود. هر هوک lifecycle که قبل از resolve شدن یک کامپوننت مسیر اتفاق بیفتد، گزینه خوبی نیست؛ زیرا در نهایت کار شما به پر کردن اسکریپت اولیه با کد مختص مسیر ختم خواهد شد.
موارد رد صلاحیت شده: beforeEech، beforeEnter.
هوکهایی که میان درخواست سرور و راهاندازی کلاینت، زائد نیستند
کد هوک lifecycle که در طی lifecycle درخواست سرور و lifecycle راهاندازی کلاینت فراخوانی شود، میتواند رد صلاحیت شود. هر زمان که ما در این هوکها برای دادهای درخواست میکنیم، کار ما یا به درخواست مجدد آن ختم میشود، یا به مجبور بودن به اضافه کردن یک شرط، و یا به بررسی این که کد مورد نظر سمت سرور را اجرا میکند یا سمت کاربر را؛ زیرا به این صورت از تکرار کد جلوگیری میشود.
موارد رد صلاحیت شده: beforeEnter، beforeRouteEnter، beforeCreate، created.
هوکهایی که بین جهتیابی کلاینت و بروزرسانی جهتیابی کلاینت با ثبات هستند
سپس میتوانیم هر هوک lifecycle که بین lifecycleهای جهتیابی کلاینت و جهتیابی بروزرسانی کلاینت باثبات هست را رد صلاحیت کنیم. اگر شما منطق درخواست خود را در این هوکها قرار دهید، در نهایت یا دادههای شما در یکی از lifecycleها درخواست نمیشوند، یا این که مجبور خواهید بود منطق درخواست خود را میان چند هوک lifecycle تکرار کنید.
موارد رد صلاحیت شده: beforeEnter، beforeRouteEnter، beforeRouteUpdate، beforeCreate، created، watch $route، beforeMount، mounted، beforeUpdate، updated.
حال چه چیزی برایمان باقی مانده است؟
آیا هوکهای onReady، beforeResolve و afterEach ما را قادر خواند ساخت تا دادهها را به صورت توصیف شده در مدل خود درخواست کنیم؟ با کمک هوکهای کامپوننت سفارشی، قطعا همینطور است.
هوکهای کامپوننت مسیر (Route Component Hooks)
هوکهای کامپوننت مسیر، توابع سفارشیای هستند که به صورت اختیاری در یک کامپوننت مسیر که میتواند در طی lifecycleهای مسیر استفاده شود، تعریف شدهاند.
امتحان کردن همه آنها با هم
ما تعریف کردهایم که در کجای lifecycleهای مسیر میخواهیم دادهها را دریافت کنیم و میدانیم که از کدام هوکهای lifecycle باید استفاده کنیم. گرچه، نمیتوانیم منطق درخواست تمام مسیرها را در هوکهای lifecycle از نوع global تعریف کنیم. در عوض، ما باید منطق هر مسیر را در کامپوننت مسیر سفارشی خودش تعریف کنیم و به هوکهای lifecycle دستور دهیم که آنها را فراخوانی کنند.
ما در lifecycle درخواست سرور خود میتوانیم از هوک lifecycle به نام onReady برای فراخوانی هوکهای کامپوننت مسیر permissions و criticalData استفاده کنیم.
ما در lifecycle راهاندازی کلاینت خود میتوانیم از هوک lifecycle به نام onReady استفاده کنیم تا هوک کامپوننت مسیر lazyData را فراخوانی کنیم. ما همچنین میتوانیم در هوکهای lifecycle به نام beforeResolve و afterEach مشترک شویم (subscribe کنیم) که هوکهای کامپوننت permissions، criticalData و lazyData را در طی جهتیابی متعاقب فراخوانی خواهد کرد.
بیایید همه چیز را به صورت یکجا در یک کد نمونه ببینیم.
کد
مثالهای کد زیر به ما نشان میدهند که راهاندازی هوکهای کامپوننت مسیر سفارشی از طریق نقاط ورود سرور و کلاینت وباپلیکیشن جهانی ما چقدر ساده است.
نقطه ورود سرور (Server Entry Point)
export default context => {
const store = createStore()
const router = createRouter()
const app = createApp(store, router)
return new Promise((resolve, reject) => {
router.onReady(resolve, reject)
router.push(context.url)
}).then(() => {
// onReady hook
const to = router.currentRoute
const components = router.getMatchedComponents(to)
// invoke the components' permissions hooks
const permissionsPromises = components.map(component => {
// allow hook to be optional
if (component.permissions) {
return component.permissions(store, to, {})
}
return Promise.resolve()
})
// Once user demonstrates they have all the permissions
// Let's start to request the critical data
return Promise.all(permissionsPromises)
.then(() => {
const criticalDataPromises = components.map(component => {
// allow hook to be optional
if (component.criticalData) {
return component.criticalData(store, to, {})
}
return Promise.resolve()
})
return Promise.all(criticalDataPromises)
})
.then(() => {
// Must wait for critical data during server request lifecycle
context.state = store.state
return app
})
})
}
نقطه ورود کلاینت (Client Entry Point)
const store = createStore()
// data populated from server
store.replaceState(window.__INITIAL_STATE__)
const router = createRouter()
const routerReady = new Promise(resolve => {
router.onReady(resolve)
}).then(() => {
// onReady hook
// request all the inital route's lazy data without blocking rendering
router.getMatchedComponents(router.currentRoute).forEach(component => {
// allow hook to be optional
if (component.lazyData) {
component.lazyData(store, router.currentRoute, {})
}
})
// subcribe to the beforeResolve hook to
// request permissions for subsequent navigation
router.beforeResolve((to, from, next) => {
const matched = router.getMatchedComponents(to)
const permissionPromises = matched.map(component => {
// allow hook to be optional
if (component.permissions) {
return component.permissions(store, to, from)
}
return Promise.resolve()
})
Promise.all(permissionPromises)
.then(() => {
next()
})
.catch(err => {
console.log(err)
next(err)
})
})
// subcribe to the afterEach hook to
// request critical data and lazy data for subsequent navigation
router.afterEach((to, from) => {
const matched = router.getMatchedComponents(to)
matched.forEach(component => {
// allow hook to be optional
if (component.criticalData) {
component.criticalData(store, to, from)
}
})
matched.forEach(component => {
// allow hook to be optional
if (component.lazyData) {
component.lazyData(store, to, from)
}
})
})
})
const app = createApp(store, router)
routerReady.then(() => {
app.$mount('#app')
})
کامپوننت مسیر نمونه
<script>
export default {
name: 'MyRouteComponent',
permissions(store, to, from) {
// request permissions data
// return a promise to resolve or reject navigation
},
criticalData(store, to, from) {
// request critical data
// return a promise so that data can finish loading during Server Request lifecycle
},
lazyData (store, to, from) {
// request lazy data
// no need to return a promise because nothing should wait on it
}
}
</script>
منفعتهای هوکهای کامپوننت مسیر
تقسیم بندی کد - تمام کدهای مختص مسیر برای درخواست دادهها میتوانند داخل کامپوننتهای مسیر قرار داشت باشند، تا به راحتی تقسیم بندی کد انجام شود.
خوانایی - هر شخصی با نگاه به یک کامپوننت مسیر میتواند به راحتی مکانهایی که دادهها برای یک مسیر درخواست میشوند را تشخیص دهد.
قابلیت نگهداری - در هنگام طراحی کامپوننتهای مسیر، کد مربوط به درخواست دادهها در مجموعهای قابل پیشبینی از توابع قرار داده شده است و در تمام lifecycleهای مسیر ما کار خواهد کرد.
کارایی - جهتیابی فقط وقتی که نیاز است، مسدود میشود و دادهها هر زمان که ممکن باشد، در طی کامپوننتهای مطابق یک مسیر به موازات هم ارسال میشوند.
کلام آخر
با این که این مقاله مدلها و نمونه کدهای مستبدی را به همراه دارد، ایدههای کلی همچنان منعطف هستند و من شما را تشویق میکنم که آنها را با اهداف خود وقف دهید.
امیدوارم که این مقاله برای شما کاربردی بوده باشد.
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید