# کامپوننت‌ها در Vue.js

پیش‌تر در سایت مطالبی در رابطه با فریم‌ورک Vue.js منتشر شده‌است. در این مطلب می‌خواهیم نگاهی بر مفهوم کامپوننت‌ها در Vue بیندازیم و نحوه‌ی استفاده از آنها را بررسی کنیم. قبل از معرفی کامپوننت‌ها اجازه

- Published: 2018-05-20
- Language: fa
- Tags: DNTips
- Canonical: https://sirwan.info/blog/fa/dntips-2852

---

> این نوشته نخستین بار در [دات‌نت تیپس](https://www.dntips.ir/post/2852) منتشر شده است.

<div class="postBody">پیش‌تر در سایت مطالبی در رابطه با فریم‌ورک <a href="https://www.dntips.ir/search?term=Vue.js">Vue.js</a> منتشر شده‌است. در این مطلب می‌خواهیم نگاهی بر مفهوم کامپوننت‌ها در Vue بیندازیم و نحوه‌ی استفاده از آنها را بررسی کنیم.<div> <br/> <div>قبل از معرفی کامپوننت‌ها اجازه دهید سیستم template در ویو را بررسی کنیم. سیستم template ویو براساس سینتکس HTML است:</div> <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="JScript" name="code">new Vue({&#10;  el: '#app',&#10;  template: '&lt;div&gt;Hello DNT&lt;/div&gt;'&#10;});</pre>
 </div>
البته استفاده از template کاملاً اختیاری است. بجای آن می‌توانیم از تابع رندر (همانند React) نیز استفاده کنیم:</div> <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="JScript" name="code">new Vue({&#10;    el: '#app',&#10;    data() {&#10;        return {&#10;            blogTitle: 'DNT'&#10;        }&#10;    },&#10;    render: function (createElement) {&#10;        return createElement('h1', this.blogTitle)&#10;    }&#10;});</pre>
 </div> <br/> </div> <div> <b>ایجاد یک کامپوننت ساده:</b> </div> <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="JScript" name="code">Vue.component('child', {&#10;    template: '&lt;div&gt;Hello DNT users&lt;/div&gt;'&#10;});</pre>
 </div>
در اینجا برای ایجاد یک کامپوننت، از تابع component استفاده کرده‌ایم؛ پارامتر اول این تابع، نام کامپوننت است و پارامتر دوم نیز یک شیء است. درون این شیء می‌توانیم قالب کامپوننت را تعیین کنیم. برای کامپوننت نیز می‌توانیم یک پارامتر ورودی را تعیین کنیم. اینکار را توسط مفهومی به نام Props می‌توانیم انجام دهیم:</div> <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="JScript" name="code">Vue.component('child', {&#10;    props: ['text'],&#10;    template: `&lt;div&gt; {{ text }} &lt;/div&gt;`&#10;});&#10;&#10;new Vue({&#10;    el: '#app',&#10;    data() {&#10;        return {&#10;            message: 'Hello DNT!'&#10;        }&#10;    }&#10;});</pre>
 </div>
اکنون می‌توانیم پارامتر موردنظر را به text، به عنوان ورودی کامپوننت ارسال کنیم (در واقع دیتای موجود در parent را به کامپوننت child ارسال کرده‌ایم):</div> <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="XML" name="code">&lt;child :text="message"&gt;&lt;/child&gt;</pre>
 </div> <br/> </div> <div> <b>اعتبارسنجی پراپرتی‌ها</b> </div> <div>برای props می‌توانیم اعتبارسنجی را نیز انجام دهیم:</div> <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="JScript" name="code">Vue.component('blogPost', {&#10;    props: {&#10;        post: {&#10;            type: Object,&#10;            required: true&#10;        }&#10;    },&#10;    template: `&lt;div&gt;&#10;                    &lt;h1&gt;{{ post.title }}&lt;/h1&gt;&#10;                    &lt;p&gt;{{ post.body }}&lt;/p&gt;&#10;               &lt;/div&gt;`&#10;});</pre>
 </div>
در اینجا نوع خاصیت post باید شیء باشد و همچنین آن را به صورت required تعریف کرده‌ایم. در این حالت اگر مقداری به غیر از شیء را به آن ارسال کنیم، خطای زیر را در کنسول دریافت خواهیم کرد:</div> <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="JScript" name="code">[Vue warn]: Invalid prop: type check failed for prop "post". Expected Object, got String.&#10;&#10;found in&#10;&#10;---&gt; &lt;BlogPost&gt;&#10;       &lt;Root&gt;</pre>
 </div> <br/> </div> <div>همچنین می‌توانیم نوع اعتبارسنجی را به صورت سفارشی نیز تعیین کنیم:</div> <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="JScript" name="code">Vue.component('blogPost', {&#10;    props: {&#10;        post: {&#10;            type: Object,&#10;            required: true,&#10;            validator: obj =&gt; {&#10;                const titleIsValid = typeof obj.title === 'string';&#10;                const bodyIsValid = typeof obj.body === 'string';&#10;                const isValid = titleIsValid &amp;&amp; bodyIsValid;&#10;                if (!isValid) {&#10;                    console.warn("prop is not valid");&#10;                    return false;&#10;                }&#10;                return true;&#10;            }&#10;        }&#10;    },&#10;    template: `&lt;div&gt;&#10;                    &lt;h1&gt;{{ post.title }}&lt;/h1&gt;&#10;                    &lt;p&gt;{{ post.body }}&lt;/p&gt;&#10;               &lt;/div&gt;`&#10;});</pre>
 </div> <br/> </div> <div> <b>تعیین مقدار پیش‌فرض برای پراپرتی</b> </div> <div>برای یک prop می‌توانیم مقدار پیش‌فرضی را نیز تعیین کنیم. یعنی در صورت عدم ارسال شیء می‌توانیم تعیین کنیم که چه شیء‌ایی در حالت پیش‌فرض نمایش داده شود:</div> <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="JScript" name="code">Vue.component('blogPost', {&#10;    props: {&#10;        post: {&#10;            type: Object,&#10;            validator: obj =&gt; {&#10;                const titleIsValid = typeof obj.title === 'string';&#10;                const bodyIsValid = typeof obj.body === 'string';&#10;                const isValid = titleIsValid &amp;&amp; bodyIsValid;&#10;                if (!isValid) {&#10;                    console.warn("prop is not valid");&#10;                    return false;&#10;                }&#10;                return true;&#10;            },&#10;            default: function() {&#10;                return {&#10;                    title: 'Vue is fun!',&#10;                    body: 'Vue is fun..................'&#10;                }&#10;            }&#10;        }&#10;    },&#10;    template: `&lt;div&gt;&#10;                    &lt;h1&gt;{{ post.title }}&lt;/h1&gt;&#10;                    &lt;p&gt;{{ post.body }}&lt;/p&gt;&#10;               &lt;/div&gt;`&#10;});</pre>
 </div> <br/> </div> <div> <b>استفاده از دیتا درون کامپوننت</b> </div> <div>درون یک کامپوننت نیز می‌توانیم یکسری دیتا را تعریف کنیم. اما باید در نظر داشته باشید که هر وهله از کامپوننت، scope مجزای خودش را دارد. در نتیجه پیشنهاد میشود دیتا حتماً به صورت یک تابع تعریف شود و همچنین داده‌های درون آن نیز در scope کامپوننت تعریف شوند:</div> <div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="JScript" name="code">data: function () {&#10;    return {&#10;        stars: 5,&#10;        hover: 5&#10;    }&#10;},</pre>
 </div>
زیرا در صورت تعریف داده‌ها در خارج از scope کامپوننت، محتویات دیتا برای دیگر وهله‌های کامپوننت‌ها نیز به اشتراک گذاشته می‌شود. به عنوان مثال فرض کنید درون کامپوننت بلاگ‌پست، یک سیستم امتیاز دهی قرار داده‌ایم:</div> <div> <div align="left" dir="ltr" style="direction: ltr;"> </div> <div align="left" dir="ltr" style="direction: ltr;"> </div> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">var dt = {&#10;    stars: 5,&#10;    hover: 5&#10;};&#10;Vue.component('blogPost', {&#10;    data: function() {&#10;        return dt;&#10;    },&#10;    props:  // as before...,&#10;&#10;    template: `&lt;div class="blog-post"&gt;&#10;                    &lt;h1&gt;{{ post.title }}&lt;/h1&gt;&#10;                    &lt;p&gt;{{ post.body }}&lt;/p&gt;&#10;                    &lt;div class="star-wrap"&gt;&#10;                        &lt;span v-for="n in 5"&#10;                            class="star"&#10;                            :class="{ full: hover &gt;= n+1 }"&#10;                            @click="stars = n+1"&#10;                            @mouseover="hover = n+1"&#10;                            @mouseout="hover = stars"&#10;                        &gt;&lt;/span&gt;&#10;                    &lt;/div&gt;&#10;               &lt;/div&gt;`&#10;});</pre>
 </div>
در این حالت خروجی زیر را خواهیم داشت:</div> <div> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> <p style="margin-left: auto; margin-right: auto;"> <img src="/img/dntips/e501320483b2d2451f7e.gif" style="display: block; margin-left: auto; margin-right: auto; cursor: default; height: 326px;"/> </p> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> <p style="margin-left: auto; margin-right: auto;">برای رفع این مشکل کافی است به اینصورت دیتا را تعریف کنیم:</p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="JScript" name="code">Vue.component('blogPost', {&#10;    data: function() {&#10;        return {&#10;             stars: 5,&#10;             hover: 5&#10;        }&#10;    },&#10;    props:  // as before...,&#10;    template: // as before&#10;});</pre>
 </div> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> <p style="margin-left: auto; margin-right: auto;"> <b>تغییر دیتا درون کامپوننت‌ها</b> </p> <p style="margin-left: auto; margin-right: auto;">تا اینجا توانستیم از کامپوننت والد داده‌هایی را به کامپوننت‌های فرزند ارسال کنیم. اکنون می‌خواهیم قابلیت تغییر دیتای تعریف شده‌ی درون کامپوننت والد را درون کامپوننت‌ها نیز داشته باشیم:</p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="JScript" name="code">Vue.component('child', {&#10;    props: ['message'],&#10;    methods: {&#10;        changeName() {&#10;            this.message = "New Name!..."&#10;        }&#10;    },&#10;    template: '#child-template'&#10;});&#10;&#10;new Vue({&#10;    el: '#app',&#10;    data() {&#10;        return {&#10;            name: 'DNT!'&#10;        }&#10;    }&#10;});</pre>
 </div> <p style="margin-left: auto; margin-right: auto;">تمپلیت کامپوننت فوق نیز به صورت x-template درون DOM تعریف شده است:</p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="XML" name="code">&lt;script type="text/x-template" id="child-template"&gt;&#10;    &lt;div&gt;&#10;        &lt;p&gt;{{ message }}&lt;/p&gt;&#10;        &lt;button @click="changeName"&gt;Change Name&lt;/button&gt;&#10;    &lt;/div&gt;&#10;&lt;/script&gt;</pre>
 </div> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> <p style="margin-left: auto; margin-right: auto;">فراخوانی کامپوننت نیز به اینصورت می‌باشد:</p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="XML" name="code">&lt;div id="app"&gt;&#10;    &lt;child :message="name"&gt;&lt;/child&gt;&#10;&lt;/div&gt;</pre>
 </div> <p style="margin-left: auto; margin-right: auto;">همانطور که مشاهده می‌کنید، دیتای name را از طریق ویژگی message توانسته‌ایم به کامپوننت child ارسال کنیم. درون تمپلیت آن نیز یک دکمه را برای تغییر مقدار این ویژگی تعریف کرده‌ایم. تغییر این ویژگی نیز یک assignment ساده است. اما اگر بر روی دکمه‌ی Change Name کلیک کنید، هشدار زیر را درون کنسول مشاهده خواهید کرد:</p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "message"&#10;&#10;found in&#10;&#10;---&gt; &lt;Child&gt;&#10;       &lt;Root&gt;</pre>
 </div> <p style="margin-left: auto; margin-right: auto;">دلیل آن نیز مشخص است؛ زیرا با تغییر این ویژگی، کامپوننت والد از وجود تغییرات مطلع نشده‌است و فقط این تغییرات، درون کامپوننت child صورت گرفته‌است. برای اطلاع‌رسانی کامپوننت والد می‌توانیم از یک ایونت ویژه استفاده کنیم و کامپوننت والد را از وجود تغییرات مطلع کنیم:</p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="JScript" name="code">changeName() {&#10;    this.message = "New Name!...",&#10;    this.$emit("change-name", this.message);&#10;}</pre>
 </div> <p style="margin-left: auto; margin-right: auto;">برای تگ child نیز این ایونت را اضافه خواهیم کرد:</p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="XML" name="code">&lt;child :message="name" @change-name="name = $event"&gt;&lt;/child&gt;</pre>
 </div> <p style="margin-left: auto; margin-right: auto;">در اینحالت با تغییر ویژگی message، مقدار دیتای name نیز بلافاصله تغییر پیدا خواهد کرد.</p> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> <p style="margin-left: auto; margin-right: auto;"> <b>Slots</b> </p> <p style="margin-left: auto; margin-right: auto;">Slot یک روش عالی برای جایگزینی محتوای درون یک کامپوننت است. فرض کنید می‌خواهیم کامپوننت‌مان به صورت زیر باشد:</p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="JScript" name="code">&lt;modal&gt;&#10;    Hello&#10;&lt;/modal&gt;</pre>
 </div> <p style="margin-left: auto; margin-right: auto;">در اینحالت باید درون تمپلیت مکان قرارگیری Hello را تعیین کنیم. اینکار را می‌توانیم با قرار دادن تگ slot انجام دهیم:</p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="JScript" name="code">Vue.component('modal', {&#10;    template: `&#10;            ...&#10;            &lt;div class="modal-body"&gt;&#10;                &lt;slot&gt;&lt;/slot&gt;&#10;            &lt;/div&gt;&#10;            ...&#10;    `&#10;});</pre>
 </div> <p style="margin-left: auto; margin-right: auto;">اکنون هر محتوایی که درون تگ modal قرار گیرد، در قسمت slot نمایش داده خواهد شد. این نوع slot به صورت پیش‌فرض می‌باشد. در واقع می‌توانیم slotها را نیز نامگذاری کنیم. به عنوان مثال یک slot برای عنوان modal، یک slot برای بدنه modal و یک slot دیگر برای فوتر modal تعریف کنیم:</p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="CSharp" name="code">Vue.component('modal', {&#10;    template: `&#10;    &lt;div class="modal fade" id="detailsModal" tabindex="-1" role="dialog" aria-labelledby="detailsModalLabel" aria-hidden="false"&gt;&#10;        &lt;div class="modal-dialog" role="document"&gt;&#10;            &lt;div class="modal-content"&gt;&#10;                &lt;div class="modal-header"&gt;&#10;                    &lt;h5 class="modal-title" id="detailsModalLabel"&gt;&#10;                        &lt;slot name="title"&gt;&lt;/slot&gt;&#10;                    &lt;/h5&gt;&#10;                    &lt;button type="button" class="close" data-dismiss="modal" aria-label="Close"&gt;&#10;                    &lt;span aria-hidden="true"&gt;&amp;times;&lt;/span&gt;&#10;                    &lt;/button&gt;&#10;                &lt;/div&gt;&#10;                &lt;div class="modal-body"&gt;&#10;                    &lt;slot name="body"&gt;&lt;/slot&gt;&#10;                &lt;/div&gt;&#10;                &lt;div class="modal-footer"&gt;&#10;                    &lt;slot name="footer"&gt;&lt;/slot&gt;&#10;                &lt;/div&gt;&#10;            &lt;/div&gt;&#10;        &lt;/div&gt;&#10;        &lt;/div&gt;&#10;    `&#10;});</pre>
 </div> <p style="margin-left: auto; margin-right: auto;">اکنون می‌توانیم محتوای مورد نظر را برای قرارگیری درون slotها تعیین کنیم:</p> <div align="left" dir="ltr" style="direction: ltr;">
<pre language="JScript" name="code">&lt;modal&gt;&#10;    &lt;template slot="title"&gt;Title&lt;/template&gt;&#10;    &lt;template slot="body"&gt;Lorem ipsum dolor sit amet.&lt;/template&gt;&#10;    &lt;template slot="footer"&gt;&#10;        &lt;button type="button" class="btn btn-secondary" data-dismiss="modal"&gt;Close&lt;/button&gt;&#10;        &lt;button type="button" class="btn btn-primary"&gt;Save changes&lt;/button&gt;&#10;    &lt;/template&gt;&#10;&lt;/modal&gt;</pre>
 </div> <p style="margin-left: auto; margin-right: auto;"> <br/> </p> </div> </div></div>
