در این مقاله به بررسی استفاده از کامپوننت ها میپردازیم. ابتدا یک فایل HTML خالی مانند کد زیر ایجاد کرده و سعی میکنیم مرحله به مرحله با اِعمال تغییرات روی آن، به هدف خود برسیم.
<html>
<head>
<title>use of components</title>
</head>
<body>
<div id="app">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
},
methods: {
}
});
</script>
</body>
</html>
کامپوننت ها بخشی مهمی از فریمورک Vue.js هستند، آنها بلوکهای شما برای ساختن برنامههای واکنش پذیر هستند که خوش بختانه برای ما، یادگیری و استفاده از آنها بسیار ساده هستند!
یک کامپوننت را به عنوان هر عنصری در صفحه وب یا برنامه خود در نظر بگیرید که می خواهید یک یا چند بار از آن استفاده کنید. کامپوننت میتواند به اندازه ی یک دکمه، یک ورودی یا به اندازه ی کل نوار منوی شما و یا حتی کل نمای یک صفحه باشد.
مزیت ایجاد یک کامپوننت این است که شما منطق شیوه نمایش آن (HTML / CSS) و شیوه تعامل آن با کاربر (فقط JS) را یک بار مینویسید و سپس فقط از آن در سراسر برنامه خود و به هر تعداد که لازم داشتید، استفاده میکنید.
طبق معمول، ما با سادهتر ین مثال ممکن، یعنی یک دکمه شروع میکنیم. بیایید ابتدا طرح اولیه یا تمپلیت (template
) این کامپوننت را تعریف کنیم و فرض کنید که اسم آن را awesome-button
گذاشتیم. برای تعریف آن از Vue.component
همانند کد زیر استفاده میکنیم:
Vue.component('awesome-button', {
template: `<button @click="clickHandler">Click me for some awesomeness</button>`,
methods: {
clickHandler() {
alert('Hello Vue.js');
}
}
});
Vue از طریق CDN
، که قبلاْ اضافه کردهایم در دسترس ما قرار گرفته است، این CDN
شامل کامپوننت هایی نیز میشود که ما در اینجا فراخوانی میکنیم. این موضوع به ما این امکان را میدهد که یک کامپوننت جدید را ایجاد کنیم. اولین پارامتری که برای کامپوننت موردنظر تنظیم میکنیم یک رشته است که اسم کامپوننت ما خواهد بود.
پارامتر دوم یک object ورودی JavaScript است، که در واقع نوعی object پیکربندی است که برای نمونه ی اصلی Vue استفاده میشود. این موضوع به این معنی است که یک template
تمام ویژگیهای یک نمونه Vue را داراست. به عبارتی هر کامپوننتی میتواند یک نمونه Vue شناخته شود.
در مثال بالا، درون template
باید اِلِمان موردنظر را قرار داد که در اینجا یک button است. دقت شود که مقدار template را به صورت یک رشته تعریف کردیم که درون علامت `` (back-tick) قرار گرفته است.
اگر همین حالا فایل HTML خود را در صفحه مرورگر باز کنید اتفاقی نمیافتد. یادتان باشد که کاری که تا الان انجام دادیم فقط ساخت یک template
بود که آن را در صفحه خود ارائه ندادیم اما در ادامه شیوه استفاده از آن را خواهید فهمید. برای انجام این کار کافی است که به ابتدای تگ <div id=”app”></div>
رفته و با توجه به اسمی که برای کامپوننت موردنظر انتخاب شده، آن را به کار برد. شیوه استفاده از آن در کد زیر نشان داده شده است.
<div id="app">
<awesome-button></awesome-button>
</div>
حال اگر صفحه مرورگر را نگاه کنید دکمه ای مشابه دکمه موجود در عکس زیر را خواهید دید:
ابتدا یک آرایه ی جدید با اسم
games
را در ()data
تعریف میکنیم.
data: {
games: [
{ name: 'Super Mario 64', console: 'Nintendo 64', rating: 4 },
{ name: 'The Legend of Zelda Ocarina of Time', console: 'Nintendo 64', rating: 5 },
{ name: 'Secret of Mana', console: 'Super Nintendo', rating: 4 },
{ name: 'Fallout 76', console: 'Multiple', rating: 1 },
{ name: 'Super Metroid', console: 'Super Nintendo', rating: 6 }
]
}
برای استفاده کردن از این آرایه قصد داریم یک کامپوننت جدید به اسم game-card
مانند کدی که در زیر نوشتیم، بسازیم.
Vue.component('game-card', {
props: ['gameData'],
template: `
<div style="border-radius: .25rem; border: 1px solid #ECECEC; width: 400px; margin: 1rem; padding: 1rem;">
<h2>{{ gameData.name }} - <small>{{ gameData.console }}</small></h2>
<span v-for="heart in gameData.rating">❤️</span>
<button @click="increaseRating">Increase Rating</button>
</div>
`,
methods: {
increaseRating() {
// this.game.rating++;
}
}
});
شیوه استفاده از این کامپوننت هم مانند مثال قبل است با این تفاوت که برای نمایش اطلاعات مورد نیاز باید از حلقه v-for
همانند کد زیر استفاده کرد.
<game-card v-for="game in games" :key="game.name"></game-card>
اگر دقت کنید پس از استفاده از این کد، همچنان هیچ خروجی مشخصی قابلمشاهده نیست. دلیل آن نیز این است که دادههای موجود در آرایه games
از طریق یک props
به تگ های داخلی کامپوننت ارجاع داده شده است. اما ما هنوز در کد خود از این props
استفاده نکردهایم.
Props
های یک کامپوننت
در واقع props
به ما کمک میکنند تا دادهها را از طریق یک کامپوننت parent
به کامپوننت های داخلی آن (child) منتقل کنیم و از آنها استفاده کنیم. در حلقه v-for
ایجاد شده روی کامپوننت، هر آبجکت از آرایه games
را درون متغیر game
می ریزیم اما در کدهای نوشته شده هیچ گونه استفاده ای از این متغیر نکردیم در واقع قصد داریم این متغیر را به صورت یک props
برای کامپوننت تعریف کنیم. برای استفاده از props
کافی است مانند کد زیر با استفاده از یک اسم دلخواه (که در اینجا game-data
است) دادههای موردنظر را روی کامپوننت تعریف کرد.
<game-card v-for="game in games" :game-data="game" :key="game.name"></game-card>
همانطور که دیدید game را به عنوان یک props با اسم game-data برای کامپوننت game-card تعریف کردیم. دقت کنید که در اینجا به دلیل این کهHTML، یک case insensitive است برای اسم props
از kebab case
به جای camel case
استفاده کردیم.
زمانی که ما یک property
را به یک نمونه vue اختصاص میدهیم معمولاً property
موردنظر را به صورت یک رشته تعریف میکنیم. حال اگر این property
یک مقدار متغیر داشته باشد (مانند کد بالا) باید قبل از آن از علامت ‘:’ استفاده کنیم ( علامت ‘:’ در واقع کوتاه شده ی ‘:v-bind’
است).
Data و Props برای یک کامپوننت
در واقع Props
ها میتوانند موضوع پیچیده ای برای کامپوننت ها باشند اما یک قانون مهم و اساسی وجود دارد که باید همیشه آن را به خاطر بسپارید که یک property
هرگز نباید از داخل یک کامپوننت تغییر داده شود. در حقیقت، اگر سعی کنید این کار را انجام دهید Vue انواع اخطارها را در کنسول به شما نشان میدهد.
اگر دقت کرده باشید ما یک متد برای کامپوننت game-card
تعریف کردیم که اسم آن ()increaseRating
بود. در این متد قصد داریم که عدد rating را افزایش دهیم. اما همانطور که گفته شد نمیتوان محتوای داخلی یک props
را در داخل کامپوننت تغییر داد بنابراین اگر کد ++this.game.rating
را از حالت کامنت خارج کنیم با زدن روی دکمه ‘Increase Rating’
مقدار rating نه تنها افزایش پیدا نکرده بلکه در کنسول مرورگر خطای زیر را نیز مشاهده خواهیم کرد.
برای رفع چنین خطایی بهتر است ابتدا اطلاعات موجود در
props
را درون یک متغیر جدید ریخته و سپس از این متغیر درون کامپوننت استفاده کرد. با این روش، دیگر مشکلی برای تغییر مقادیر props
وجود ندارد. فقط دقت کنید که اسم انتخاب شده برای متغیر جدید نباید با اسم props
یکی باشد. کد زیر را درون کامپوننت game-card
تعریف کنید و در متد ()increaseRating
به جای game از متغیر تعریف شده جدید (newGameData
) استفاده کنید.
data() {
return {
newGameData: {...this.gameData}
}
},
همانطور که مشاهده میکنید دیگر خبری از خطای کنسول نیست زیرا همه ی مقادیر موجود در props
را در متغیر جدید ریختیم و در متد تعریف شده نیز از این متغیر مانند کد زیر استفاده کردیم. اما قبل از نوشتن ادامه ی کدها، بهتر است دلیل قرار دادن ‘…’قبل از this.gameData
را توضیح دهم. این علامت در واقع spread operator نامیده میشود که به این معناست که همه اطلاعات موجود در this.gameData
درون newGameData
کپی میشوند.
increaseRating() {
this.newGameData.rating++;
}
دقت شود که ما هنوز HTML خود را بهروزرسانی نکردیم به همین دلیل است که با کلیک کردن روی دکمه افزایش rating هیچ اتفاقی نمیافتد. برای بهروزرسانی کردن HTML باید درون template
به جای props
تعریف شده (gameData
) از متغیری جدیدی که تعریف کردیم، استفاده کرد. به عبارتی به جای gameData
باید newGameData
را مانند کد زیر قرار دهیم.
<div style="border-radius: .25rem; border: 1px solid #ECECEC; width: 400px; margin: 1rem; padding: 1rem;">
<h2>{{ newGameData.name }} - <small>{{ newGameData.console }}</small></h2>
<span v-for="heart in newGameData.rating">❤️</span>
<button @click="increaseRating">Increase Rating</button>
</div>
حالا اگر روی دکمه ی موردنظر کلیک کنید خواهید دید که با هر کلیک تعداد قلب ها افزایش مییابد. البته دقت کنید که تعداد قلب ها درون props
تغییری نمیکند و فقط تعداد آنها در متغیر newGameData
که یک کپی از props
است افزایش پیدا میکند.
تمام کدی که در این مقاله با هم زدیم به صورت زیر است. امیدوارم به اندازه ی کافی شما را با کاربرد کامپوننت ها آشنا کرده باشم. منتظر مقالات بعدی ما باشید.
<html>
<head>
<title>use of components</title>
</head>
<body>
<div id="app">
<awesome-button></awesome-button>
<game-card v-for="game in games" :game-data="game" :key="game.name"></game-card>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('awesome-button', {
template: `<button @click="clickHandler">Click me for some awesomeness</button>`,
methods: {
clickHandler() {
alert('Hello Vue.js');
}
}
});
Vue.component('game-card', {
props: ['gameData'],
template: `
<div style="border-radius: .25rem; border: 1px solid #ECECEC; width: 400px; margin: 1rem; padding: 1rem;">
<h2>{{ newGameData.name }} - <small>{{ newGameData.console }}</small></h2>
<span v-for="heart in newGameData.rating">❤️</span>
<button @click="increaseRating">Increase Rating</button>
</div>
`,
data() {
return {
newGameData: {...this.gameData}
}
},
methods: {
increaseRating() {
this.newGameData.rating++;
}
}
});
const app = new Vue({
el: '#app',
data: {
games: [
{ name: 'Super Mario 64', console: 'Nintendo 64', rating: 4 },
{ name: 'The Legend of Zelda Ocarina of Time', console: 'Nintendo 64', rating: 5 },
{ name: 'Secret of Mana', console: 'Super Nintendo', rating: 4 },
{ name: 'Fallout 76', console: 'Multiple', rating: 1 },
{ name: 'Super Metroid', console: 'Super Nintendo', rating: 6 }
]
}
});
</script>
</body>
</html>