سلام دوستان. مفاهیم و ویژگی‌هایی توی ویوو یا هر فریم‌ورکی وجود داره که مشابه هم هستن و گاها یک توسعه‌دهنده برای یک موضوع، چند تا راه‌حل در اختیار داره. اما این خوب نیست. اگه مسئله رو خوب بشناسیم، فقط یک راه‌حل باقی می‌مونه! و دلیلی وجود نداره که دو ویژگی با عملکردهای یکسان توی یک برنامه وجود داشته باشن. برای مثال توی ویوو پراپرتی‌های computed، متدها و watcher ها شباهت زیادی به هم دارن. اما تو چه شرایطی مطمئن میشیم که فقط باید مثلا از پراپرتی‌های computed استفاده کنیم؟ امروز میریم که متوجه بشیم.

توی این پست منظور از نمونه همون Vue Instance یا قسمت ویوومدل (vm) برنامه هست.

 

کد زیر رو در نظر بگیرید:

<div id="app">
    {{ message }}
</div>

message یک عضو (پراپرتی) از data هست که داریم اون رو نمایش می‌دیم. اعضای data اطلاعات خام هستن و باید خام بمونن. اگه بخوایم message رو بر اساس نیازمون نشون بدیم:

<div id="app">
    {{ message.split('').reverse().join('') }}
</div>

به نظرتون یک توسعه‌دهنده‌ی دیگه می‌دونه message به چه صورتی داره نمایش داده میشه؟ با یکم صرف زمان و محاسبات ذهنی می‌تونه متوجه بشه که داریم رشته message رو به صورت معکوس نشون میدیم.

اگه بخوایم این رشته‌ی معکوس رو جاهای دیگه هم نشون بدیم چطور؟

<div id="app">
    {{ message.split('').reverse().join('') }}
    <!-- -->
    {{ message.split('').reverse().join('') }}
    <!-- -->
    {{ message.split('').reverse().join('') }}
</div>

چقدر کد تکراری! بدتر اینکه اگه بخوایم کارکتر اول message با حرف بزرگ لاتین نوشته بشه باید همه‌ی اون تکرارها رو ویرایش کنیم! اینجاست که پراپرتی‌های Computed به کار ما میان. ما می‌تونیم روی این پراپرتی خام (message) یک بار همه‌ی این محاسبات رو انجام بدیم و بعد از این پراپرتی محاسبه شده رو توی View استفاده کنیم. به این پراپرتی جدید میگن Computed یا محاسبه‌شده.

 

پراپرتی‌های Computed

این پراپرتی‌ها رو توی نمونه تعریف می‌کنیم. ابتدا توی نمونه مثل خط ۶ کد زیر یک key به اسم computed تعریف می‌کنیم. این پراپرتی باید یک آبجکت باشه که توی اون می‌تونیم پراپرتی‌های computed رو بصورت تابع تعریف کنیم. مثل خط ۷:

const app = new Vue({
    el: "#app",
    data: {
        message: "Umbrella"
    },
    computed: {
        reversedMsg: function() {
            return this.message.split('').reverse().join('');
        }
    }
});

ما اسم این پراپرتی محاسبه‌شده رو گذاشتیم reversedMsg. حالا توی هر جایی از برنامه می‌تونیم از اون درست مثل یک عضو data استفاده کنیم:

<div id="app">
    {{ reversedMsg }}
    <!-- output: allerbmU -->
</div>

یا توی نمونه و بیرون از View:

const app = new Vue({
    // ...
    mounted() {
        alert(this.reversedMsg); // allerbmU
    },
    // ...
});

alert(app.reversedMsg); // allerbmU

 

اگه مقدار پراپرتی خام message رو تغییر بدیم، پراپرتی محاسبه‌شده‌ی reversedMsg هم بصورت خودکار از این تغییرات با خبر میشه:

app.message = "Step on no pets";

console.log(app.reversedMsg); // step on no petS

 

Setter در پراپرتی‌های computed

ابتدا با مفاهیم توابع getter و setter آشنا بشیم. این مفهوم کلی هست و توی همه‌ی زبان‌ها وجود داره و قابل اجرا هست. برای یک عضو (پراپرتی) مثلا x می‌تونیم یک تابع getter درست کنیم تا x براساس فرمت دلخواه ما نشون داده بشه. و بلعکس می‌تونیم برای x یک تابع setter درست کنیم تا هنگامی مقدار x داره تغییر می‌کنه، بر اساس محاسبات خودمون تغییر کنه.

تا الان پراپرتی‌های computed ما فقط getter بودن. یعنی برای message فقط فر‌آیند نمایش براساس فرمت دلخواه رو نوشتیم. می‌تونیم تابع setter رو اضافه کنیم تا هنگامی که مقدار پراپرتی computed ما از بیرون می‌خواد تغییر کنه، بر اساس محاسبات دلخواه ما این اتفاق بیوفته. کد زیر رو ببینید:

const app = new Vue({
    data: {
        message: "Umbrella"
    },
    computed: {
        reversedMsg: {
            get() {
                return this.message.split('').reverse().join('');
            },
            set(newValue) {
                if (typeof newValue !== 'string') {
                    alert('Failed. String please!');
                    return;
                }

                this.message = newValue;
            }
        }
    }
});

همونطور که می‌بینیم نوع reversedMsg دیگه تابع نیست. بلکه یک آبجکت هست که توی اون دو متد به اسم get() و set() تعریف کردیم. بعد، کاری که قبلا پراپرتی reversedMsg انجام میداد (معکوس کردن) رو بردیم توی متد get. و نهایتا توی متد set گفتیم که برای آپدیت شدن مقدار این پراپرتی، فقط نوع رشته رو قبول کن. در غیر این صورت مانع انجام این کار بشو.

 

تفاوت متدها و پراپرتی‌های computed

شاید به ذهنتون رسیده باشه که چرا از متدها استفاده نمی‌کنیم. اگه با متد‌ها آشنایی ندارین این قسمت رو ببینین. کد زیر دقیقا مشابه کار پراپرتی‌های computed رو انجام میده:

<div id="app">
    {{ reverseMsg() }}
</div>

<script>
const app = new Vue({
    //...
    methods: {
        reverseMsg() {
            return this.message.split('').reverse().join('');
        }
    }
});
</script>

خروجی کد بالا دقیقا مثل پراپرتی‌های computed هست. اما چیزی که پشت پرده اتفاق می‌افته متفاوت هست. یکی از ویژگی‌های منحصر به فرد پراپرتی‌های computed، کش کردن خروجی هست.

پراپرتی‌ محاسبه شده‌ی reversedMsg رو درنظر بگیرید که از mesage داره استفاده می‌کنه. محاسبات توی تابع reversedMsg() فقط و فقط یک بار اجرا و سپس خروجی کش میشه. پس با n بار استفاده از اون پراپرتی، محاسبات فقط یک بار انجام میشه. مگر اینکه مقدار message تغییر کنه که در این صورت محاسبات دوباره انجام میشه:

<div id="app">
    {{ reversedMsg }}
    {{ reversedMsg }}
    {{ reversedMsg }}
    {{ reversedMsg }}
    {{ reversedMsg }}
</div>

<script>
const app = new Vue({
    el: "#app",
    data: {
        message: "Umbrella"
    },
    computed: {
        reversedMsg: function() {
            alert();
            return this.message.split('').reverse().join('');
        }
    }
});
</script>

توی کد بالا توی تابع reversedMsg() یک alert نوشتم که این نکته رو تست کنیم. اگه این کد رو امتحان کنیم می‌بینیم که فقط یک بار alert می‌گیریم. در صورتی که ۵ بار از اون پراپرتی داریم استفاده می‌کنیم.

همچنین چون تابع reversedMsg() فقط به message وابسته هست، فقط درصورتی دوباره محاسبات رو انجام میده که فقط مقدار message تغییر کنه، نه بقیه‌ی اعضای data.

اما توی متدها قضیه فرق می‌کنه:

<div id="app">
    {{ reverseMsg() }}
    {{ reverseMsg() }}
    {{ reverseMsg() }}
    {{ reverseMsg() }}
    {{ reverseMsg() }}
</div>

<script>
const app = new Vue({
    el: "#app",
    data: {
        message: "Umbrella"
    },
    methods: {
        reverseMsg: function() {
            alert();
            return this.message.split('').reverse().join('');
        }
    }
});
</script>

alert توی متد reverseMsg() به ازای هر بار استفاده از این متد تکرار میشه. اوضاع می‌تونه وخیم‌تر هم بشه. با هر بار رندر شدن View، متد reverseMsg() هم دوباره اجرا میشه. رندر شدن View هم به سادگی تغییر دادن یک عضو data صورت می‌گیره:

<div id="app">
    {{ reverseMsg() }}
    {{ reverseMsg() }}
    {{ reverseMsg() }}
    {{ reverseMsg() }}
    {{ reverseMsg() }}

    {{ letter }}
</div>

<script>
const app = new Vue({
    el: "#app",
    data: {
        message: "Umbrella",
        letter: "Roses are #FF0000",
    },
    methods: {
        reverseMsg: function() {
            alert();
            return this.message.split('').reverse().join('');
        }
    }
});
</script>

توی کد بالا اگه مقدار letter رو تغییر بدیم‌:

app.letter = "Violets are #0000FF"

View هم دوباره رندر و متد reverseMsg() پنج بار دیگه اجرا میشه! حالا تصور کنین متد reverseMsg() یک عملیات سنگین انجام میده. مثلا درخواست ای‌جکس. و با هر بار رندر شدن View تصور کنین چه اتفاقی می‌افته ¯\_(ツ)_/¯

 

خب دوستان این قسمت هم به پایان رسید. روزتون خوش 😉 ✌️