


آموزش Components API در Vue.js
Vue یک فریمورک پیشرفته برای ساخت رابط کاربری یا UI است. این فریمورک از ابتدا به گونهای طراحی شده است که به صورت تدریجی قابل توسعه بوده و میتواند به راحتی بین یک کتابخانه و یک فریمورک بسته به موارد استفاده توسعه یابد.
Vue یک فریمورک پیشرفته برای ساخت رابط کاربری یا UI است. این فریمورک از ابتدا به گونهای طراحی شده است که به صورت تدریجی قابل توسعه بوده و میتواند به راحتی بین یک کتابخانه و یک فریمورک بسته به موارد استفاده توسعه یابد.
این فریمورک حاوی یک کتابخانه مرکزی قابلدسترسی است که فقط بر روی لایه نمایش تمرکز دارد، و یک اکوسیستم نیز در این فریمورک تعبیه شده است که از کتابخانههای مورد پشتیبانی جهت کاهش پیچیدگی برنامههای سنگین و تکصفحهای، استفاده میکند. کامپوننتها در Vue یکی از اجزای مهم ساختار کد دستوری هستند که با ایجاد ارتباط بین اجزای برنامه، انعطافپذیری بیشتر و قابلیت تغییر را برای اپلیکیشن نهایی فراهم میکند.
به مجموعه کامپوننتهای API در محیط فریمورک Vue، Composition API نیز گفته میشود. در این مقاله سعی خواهیم کرد در مورد کامپوننتهای API و نحوه ایجاد آنها در Vue مطالبی را ارائه دهیم.
عملکرد Composition API چیست؟
ایجاد کامپوننتها در Vue به ما این امکان را میدهد که بخشهای تکرارپذیر رابط کاربری را به همراه عملکرد آن از قطعات کد دستوری برنامه استخراج کنیم. این ویژگی به تنهایی میتواند برنامه طراحی شده را از نظر قابلیت نگهداری و انعطافپذیری ارتقا دهد.
با این حال، تجربه کاربران و توسعهدهندگان ثابت کرده است که این ویژگی به تنهایی کافی نیست، به خصوص زمانی که برنامه حجیم و سنگین است. زمانی که برنامه سنگین است، تعداد زیادی کامپوننت در برنامه وجود دارد که باعث میشود اشتراکگذاری و استفاده مجدد از کدها دشوار باشد.
بیایید تصور کنیم که در برنامه نوشته شده، لیستی از مخازن یک کاربر خاص نمایش داده شده است. همچنین فرض کنید برنامه مورد نظر دارای قابلیت جستجو و فیلترسازی است. در این صورت کامپوننتی که عملیات جستجو و فیلترینگ را در برنامه انجام میدهد، میتواند به صورت زیر تعریف گردد:
// src/components/UserRepositories.vue export default { components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList }, props: { user: { type: String, required: true } }, data () { return { repositories: [], // 1 filters: { ... }, // 3 searchQuery: '' // 2 } }, computed: { filteredRepositories () { ... }, // 3 repositoriesMatchingSearchQuery () { ... }, // 2 }, watch: { user: 'getUserRepositories' // 1 }, methods: { getUserRepositories () { // using `this.user` to fetch user repositories }, // 1 updateFilters () { ... }, // 3 }, mounted () { this.getUserRepositories() // 1 } }
این کامپوننت چندین وظیفه دارد:
• دریافت مخازن از یک API خارجی برای یک نام کاربری خاص و رفرش کردن آن در صورت تغییر کاربر
• جستجوی مخازن با استفاده از رشته SearchQuery
• فیلتر کردن مخازن با استفاده از آبجکت filters
سازماندهی منطقها به کمک گزینههای کامپوننت (data، computed، methods، watch) در بیشتر موارد قابل پیادهسازی است. با این حال، هنگامی که کامپوننتها بزرگتر میشوند، منطقهای سازماندهی نشده نیز بیشتر خواهند شد.
منطقها اجزای یک برنامه در محیط Vue هستند که وظیفه اجرای عملیات منطقی و محاسباتی را بر عهد دارند. بنابراین عدم سازماندهی این منطقها ممکن است باعث شود که کامپوننتهای برنامه از نظر خواندن، درک، و مدیریت برای کاربران و توسعهدهندگان، به خصوص افراد کمتجربه، دشوار به نظر برسند.
مثال ارائه شده در بالا دارای یک کامپوننت بزرگ است که به معنی وجود منطقهای سازماندهی نشده زیادی در این برنامه است. چنین پراکندگی و عدم سازماندهی منطقها در محیط برنامه Vue درک و حفظ کامپوننت را دشوار خواهد کرد. علاوه بر این، هنگام کار بر روی یک منطق، باید دائماً بین بلوکهای مجاور در برنامه سوئیچ کرد تا بتوان منطق مورد نظر را به درستی تفسیر نمود.
اگر بتوان کدهای مربوط به منطقهای سازماندهی نشده را با هم ترکیب کرد، کار آسان خواهد شد. این دقیقاً همان وظیفهای است که Composition API در برنامه Vue بر عهده دارد. به عبارت سادهتر، ویژگی Composition API در Vue امکان ادغام منطقهای مختلف و پراکنده در سطح برنامه را فراهم میکند، به این ترتیب خواندن، تفسیر و بهکارگیری کامپوننت در برنامه مورد نظر سادهتر خواهد شد.
ایجاد Composition API در Vue
اکنون که با عملکرد Composition API آشنا شدیم، میتوان اصول عملکرد آن را شناخت. برای شروع به مکانی نیاز داریم که بتوانیم از آن برای پیادهسازی Composition API استفاده کنیم. در کامپوننت Vue، این مکان را setup مینامند.
گزینه کامپوننتsetup قبل از ایجاد کامپوننت، و بلافاصله پس از اجرایprops در برنامه، اجرا میشود و به عنوان نقطه ورودی برای Composition API ها عمل میکند. نکته مهم این است که میبایست از بهکارگیری this در گزینهsetup اجتناب نمود به این دلیل که این عبارت به نمونه کامپوننت (component instance) اشاره دارد.
گزینه setup میبایست از نوع تابع تعریف شود که گزارههای props و context را به عنوان ورودی میپذیرد. در ادامه گزینهsetup را به کامپوننت موجود در مثال اولمان اضافه خواهیم کرد.
// src/components/UserRepositories.vue export default { components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList }, props: { user: { type: String, required: true } }, setup(props) { console.log(props) // { user: '' } return {} // anything returned here will be available for the rest of the component } // the "rest" of the component }
حال با اضافه شدن گزینه به کامپوننت، میتوان منطق سازماندهی نشده اول را به کمک کامپوننت مورد نظر استخراج کرد. ابتدا از واضحترین بخشها شروع میکنیم که عبارتاند از:
• لیست مخازن
• تابع بهروزرسانی لیست مخازن
• لیست و تابع بهروزرسانی مخازن به صورت همزمان. در این حالت هر دو منطق توسط سایر گزینههای کامپوننت نیز قابل دسترسی هستند.
// src/components/UserRepositories.vue `setup` function import { fetchUserRepositories } from '@/api/repositories' // inside our component setup (props) { let repositories = [] const getUserRepositories = async () => { repositories = await fetchUserRepositories(props.user) } return { repositories, getUserRepositories // functions returned behave the same as methods } }
این یک نقطه شروع است، با این تفاوت که هنوز عملیات ادغام منطقها کامل نشده است زیرا متغیر مخازن از نوع واکنشی یا ری اکتیو نیست. اگر متغیر مخزن از نوع ری اکتیو نباشد به این معنی است که لیست مخزن خالی است پس باید این مسئله را ابتدا حل کرد تا بتوان به منطقها دسترسی پیدا نمود.
ایجاد متغیر واکنشی با استفاده از ref
در Vue 3.0 میتوان هر متغیر را در هر جایی با استفاده از تابعref واکنشی کرد. به مثال زیر توجه کنید:
import { ref } from 'vue' const counter = ref(0)
تابعref آرگومان متغیر را میگیرد و آن را به یک آبجکت پیچیده با مشخصه عددی (Value) برمیگرداند. به این ترتیب میتوان از آن آبجکت برای دسترسی به متغیر واکنشی یا ری اکتیو استفاده کرد.
import { ref } from 'vue' const counter = ref(0) console.log(counter) // { value: 0 } console.log(counter.value) // 0 counter.value++ console.log(counter.value) // 1
برگرداندن یا تغییر دادن مقادیر در یک آبجکت ممکن است غیرضروری به نظر برسد اما برای حفظ یکپارچگی انواع مختلف داده در زبان جاوا اسکریپت، این کار الزامی است. دلیل این مسئله این است که مقادیر در آبجکت در زبان برنامهنویسی جاوا اسکریپت بر اساس نوعشان (مثل Value، Number، String و غیره) شناخته میشوند پس حفظ یکپارچگی مقادیر آبجکتها در این زبان ضروری است.
با یکپارچهسازی مقادیر آبجکتها در برنامه میتوان آبجکت مورد نظر را به صورت یک متغیر واکنشی یا ری اکتیو در کل برنامه جابجا نمود. برای درک بهتر به مثال خود باز میگردیم و یک متغیر مخزن واکنشی را در آن ایجاد میکنیم:
// src/components/UserRepositories.vue `setup` function import { fetchUserRepositories } from '@/api/repositories' import { ref } from 'vue' // in our component setup (props) { const repositories = ref([]) const getUserRepositories = async () => { repositories.value = await fetchUserRepositories(props.user) } return { repositories, getUserRepositories } }
در این صورت هر بار با فراخوانی گزاره getUserRepositories، متغیرrepositories بهروزرسانی شده و تغییرات برنامه نمایش داده میشود. در این حالت کامپوننت به صورت زیر خواهد بود:
// src/components/UserRepositories.vue import { fetchUserRepositories } from '@/api/repositories' import { ref } from 'vue' export default { components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList }, props: { user: { type: String, required: true } }, setup (props) { const repositories = ref([]) const getUserRepositories = async () => { repositories.value = await fetchUserRepositories(props.user) } return { repositories, getUserRepositories } }, data () { return { filters: { ... }, // 3 searchQuery: '' // 2 } }, computed: { filteredRepositories () { ... }, // 3 repositoriesMatchingSearchQuery () { ... }, // 2 }, watch: { user: 'getUserRepositories' // 1 }, methods: { updateFilters () { ... }, // 3 }, mounted () { this.getUserRepositories() // 1 } }
تا اینجا منطقهای پراکنده در برنامه مورد نظر یکپارچهسازی شدهاند. آنچه باقی میماند فراخوانی getUserRepositories در هوک مورد نظر و راهاندازی یک watcher است تا هر زمان که کاربر تغییر میکند این پروِسه مجدداً انجام شود. برای این منظور از هوک lifecycle استفاده میکنیم.
ثبت هوک Lifecycle در کامپوننت setup
برای تکمیل ویژگی Composition API در مقایسه با Options API، به روشی برای ثبت هوکهای lifecycle در کامپوننت setup نیاز است. این کار به لطف ویژگیهای جدید ارائه شده در فریمورک Vue امکانپذیر است.
هوکهای lifecycle در Composition AP دارای نامهای مشابه Options API هستند، اما پیشوند آنها on است: مثلاً mounted در Composition AP به صورت onMounted نامگذاری میشود. این توابع هر تابعی را که با فراخوانی هوک توسط کامپوننت اجرا میشود، خواهند پذیرفت. در ادامه هوک lifecycle را به تابعsetup اضافه میکنیم:
// src/components/UserRepositories.vue `setup` function import { fetchUserRepositories } from '@/api/repositories' import { ref, onMounted } from 'vue' // in our component setup (props) { const repositories = ref([]) const getUserRepositories = async () => { repositories.value = await fetchUserRepositories(props.user) } onMounted(getUserRepositories) // on `mounted` call `getUserRepositories` return { repositories, getUserRepositories } }
واکنش نسبت به تغییرات کاربر با استفاده از تابعwatch
حال باید نسبت به تغییرات ایجاد شده در مشخصه user واکنش نشان دهیم. برای این منظور از تابع مستقل watch استفاده خواهیم کرد. درست مانند نحوه راهاندازی یک watcher در مشخصه user در داخل کامپوننت مورد با استفاده از گزینه watch ، میتوان این کار را با استفاده از تابع watch که از Vue ایمپورت شده است، انجام داد. سه آرگومان در این تابع قابلتعریف است:
• تابع Reactive Reference یا getter که میخواهیم آن را تماشا کنیم
• تابع فراخوانی
• گزینههای پیکربندی اختیاری
در ادامه عملکرد تابع watch را بررسی خواهیم کرد.
import { ref, watch } from 'vue' const counter = ref(0) watch(counter, (newValue, oldValue) => { console.log('The new counter value is: ' + counter.value) })
در این مثال، هرگاه counter تغییر کند، به عنوان مثالcounter.value = 5 ، تابع watch بازخوانی (آرگومان دوم) را اجرا خواهد کرد، مثلاً در مثال فوق 'The new counter value is: 5' را در کنسول ثبت میکند.
export default { data() { return { counter: 0 } }, watch: { counter(newValue, oldValue) { console.log('The new counter value is: ' + this.counter) } } }
حال که عملکرد تابع watch را فهمیدید، اجازه دهید آن را به مثال اولمان اضافه کنیم:
// src/components/UserRepositories.vue `setup` function import { fetchUserRepositories } from '@/api/repositories' import { ref, onMounted, watch, toRefs } from 'vue' // in our component setup (props) { // using `toRefs` to create a Reactive Reference to the `user` property of `props` const { user } = toRefs(props) const repositories = ref([]) const getUserRepositories = async () => { // update `props.user` to `user.value` to access the Reference value repositories.value = await fetchUserRepositories(user.value) } onMounted(getUserRepositories) // set a watcher on the Reactive Reference to user prop watch(user, getUserRepositories) return { repositories, getUserRepositories } }
احتمالاً در این برنامه، متوجه استفاده از toRefs در بالای setup شدهاید. این تابع به منظور اطمینان از این مسئله استفاده شده است کهwatcher به تغییرات ایجاد شده در پروب user واکنش نشان میدهد.
مشخصههای تابع مستقلcomputed
مشابه ref و watch، ویژگیهای محاسبهشده نیز میتوانند از محیط خارج از کامپوننت Vue به وسیله تابع computed ایمپورت شوند. به عنوان مثال کد برنامه زیر را در نظر بگیرید:
import { ref, computed } from 'vue' const counter = ref(0) const twiceTheCounter = computed(() => counter.value * 2) counter.value++ console.log(counter.value) // 1 console.log(twiceTheCounter.value) // 2
در این مثال، تابع computed یک Reactive Reference فقط خواندنی را به خروجی فراخوانی شده به عنوان اولین آرگومان برمیگرداند. برای دسترسی به مقدار متغیر محاسبه شده جدید، باید از ویژگی .value همانند ref استفاده کنیم. با اضافه کردن این تابع به کامپوننت setup ، کد برنامه زیر حاصل میشود.
// src/components/UserRepositories.vue `setup` function import { fetchUserRepositories } from '@/api/repositories' import { ref, onMounted, watch, toRefs, computed } from 'vue' // in our component setup (props) { // using `toRefs` to create a Reactive Reference to the `user` property of props const { user } = toRefs(props) const repositories = ref([]) const getUserRepositories = async () => { // update `props.user` to `user.value` to access the Reference value repositories.value = await fetchUserRepositories(user.value) } onMounted(getUserRepositories) // set a watcher on the Reactive Reference to user prop watch(user, getUserRepositories) const searchQuery = ref('') const repositoriesMatchingSearchQuery = computed(() => { return repositories.value.filter( repository => repository.name.includes(searchQuery.value) ) }) return { repositories, getUserRepositories, searchQuery, repositoriesMatchingSearchQuery } }
حال با اضافه کردن این تابع به مثال اولیه خواهیم داشت:
// src/components/UserRepositories.vue import useUserRepositories from '@/composables/useUserRepositories' import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch' import { toRefs } from 'vue' export default { components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList }, props: { user: { type: String, required: true } }, setup (props) { const { user } = toRefs(props) const { repositories, getUserRepositories } = useUserRepositories(user) const { searchQuery, repositoriesMatchingSearchQuery } = useRepositoryNameSearch(repositories) return { // Since we don’t really care about the unfiltered repositories // we can expose the filtered results under the `repositories` name repositories: repositoriesMatchingSearchQuery, getUserRepositories, searchQuery, } }, data () { return { filters: { ... }, // 3 } }, computed: { filteredRepositories () { ... }, // 3 }, methods: { updateFilters () { ... }, // 3 } }
و در نهایت فرآیند کامل میشود. به این ترتیب میتوان یک کامپوننت API را در Vue تعریف کرده و توابع مختلف آن را ایجاد نمود.