Skip to content

الخاصيات المحسوبة

مثال بسيط

التعبيرات المدمجة في القالب هي طريقة جيدة، لكنها موجهة فقط للعمليات البسيطة. إذا وضعنا الكثير من الشيفرات داخل القالب فإنه سيصبح معقد وصعب الصيانة. على سبيل المثال، إذا كان لدينا كائن مصفوفة متداخلة:

js
export default {
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Advanced Guide',
          'Vue 3 - Basic Guide',
          'Vue 4 - The Mystery'
        ]
      }
    }
  }
}
js
const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

و نريد عرض رسائل مختلفة بناءً على author (المؤلف) إذا كان لديه كتب أو لا:

template
<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>

عند هذه النقطة، يصبح القالب مزدحمًا قليلاً. يجب أن ننظر إليه لوهلة قبل أن ندرك أنه يقوم بحساب معين استنادًا إلى author.books. و أهم شيء، نحن ربما لا نريد تكرار نفس الحساب إذا كنا بحاجة إلى تضمينه في القالب أكثر من مرة.

لهذا السبب، في المعالجة المعقدة التي تتضمن بيانات تفاعلية متداخلة، يُوصى باستخدام خاصية محسوبة. هذا المثال السابق مع إعادة تصميمه بهاته الخاصية:

js
export default {
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Advanced Guide',
          'Vue 3 - Basic Guide',
          'Vue 4 - The Mystery'
        ]
      }
    }
  },
  computed: {
    //  خاصية محسوبة مُحصلة
    publishedBooksMessage() {
    // `this` يشير إلى  نسخة المكون
      return this.author.books.length > 0 ? 'نعم' : 'لا'
  }
}
template
<p>هل نشر كتبا؟</p>
<span>{{ publishedBooksMessage }}</span>

اختبرها في حقل التجارب

هنا صرحنا بخاصية محسوبة مسماة publishedBooksMessage.

جرب تغيير قيمة books في البيانات و سترى كيف تتغير publishedBooksMessage وفقا لذلك.

يمكنك ربط الخصائص المحسوبة كبيانات في القوالب مثل أي خاصية عادية. يعرف Vue أن this.publishedBooksMessage يعتمد على this.author.books، لذلك سيقوم بتحديث أي ربط يعتمد على this.publishedBooksMessage عند تغيير this.author.books.

الق نظرة على: إضافة النوع إلى الخصائص المحسوبة

vue
<script setup>
import { reactive, computed } from 'vue'

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

//مرجع خاصية محسوبة 
const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'نعم' : 'لا'
})
</script>

<template>
  <p>هل نشر كتبا؟</p>
  <span>{{ publishedBooksMessage }}</span>
</template>

اختبرها في حقل التجارب

Here we have declared a computed property publishedBooksMessage. The computed() function expects to be passed a getter function, and the returned value is a computed ref. Similar to normal refs, you can access the computed result as publishedBooksMessage.value. Computed refs are also auto-unwrapped in templates so you can reference them without .value in template expressions.

خاصية محسوبة تتبع تلقائيًا إعتمادياتها التفاعلية. يعرف Vue أن حساب publishedBooksMessage يعتمد على author.books، لذلك سيقوم بتحديث أي ربط يعتمد على publishedBooksMessage عند تغيير author.books.

الق نظرة على: إضافة النوع إلى الخاصيات المحسوبة

تخزين بالخاصيات المحسوبة مقابل التوابع

ربما لاحظت أننا يمكننا الوصول إلى نفس النتيجة من خلال استدعاء تابع في التعبير:

template
<p>{{ calculateBooksMessage() }}</p>
js
// داخل المكون
methods: {
  calculateBooksMessage() {
    return this.author.books.length > 0 ? 'Yes' : 'No'
  }
}
js
// داخل المكون
function calculateBooksMessage() {
  return author.books.length > 0 ? 'Yes' : 'No'
}

بدلا من خاصية محسوبة، يمكننا تعريف نفس الدالة كتابعة. من أجل الحصول على النتيجة النهائية، فإن كلا الطريقتين تفيان بنفس الغرض. ومع ذلك، فإن الفرق هو أن الخاصيات المحسوبة مخزنة بناءً على إعتمادياتها التفاعلية. ستقوم خاصية محسوبة بإعادة التقييم فقط عندما تتغير بعض إعتمادياتها التفاعلية. هذا يعني أنه بمجرد عدم تغير author.books، فإن الوصول المتعدد إلى publishedBooksMessage سيعيد النتيجة المحسوبة مسبقًا بدون الحاجة إلى تشغيل الدالة المحصلة مرة أخرى.

هذا يعني أيضا أن الخاصية المحسوبة التالية لن تَتحيَّن أبدا، لأن ()Date.now ليست إعتمادية تفاعلية:

js
computed: {
  now() {
    return Date.now()
  }
}
js
const now = computed(() => Date.now())

بالمقارنة، استدعاء تابع سيقوم بتشغيل الدالة دائمًا عندما يحدث إعادة التصيير.

لماذا نحتاج إلى التخزين؟ تصور أنه لدينا خاصية محسوبة مُكْلِفة مسماة list، والتي تتطلب المرور عبر جميع عناصر مصفوفة كبيرة والقيام بالكثير من الحسابات. ثم قد نملك خاصيات محسوبة أخرى تعتمد على list بدورها. بدون التخزين، سنقوم بتشغيل الدالة المحصلة على list عدة مرات أكثر من الضروري! في حالات لا تريد التخزين، استدع تابع بدلاً من ذلك.

الخاصيات المحسوبة القابلة للكتابة

الخاصيات المحسوبة غير قابلة للتعيين افتراضيًا. إذا حاولت تعيين قيمة جديدة لخاصية محسوبة، ستحصل على تحذير خلال وقت التشغيل. في الحالات النادرة التي تحتاج إلى خاصية محسوبة "قابلة للكتابة"، يمكنك إنشاء واحدة من خلال توفير الدالتين المُحصِّلة و المعيّنة:

js
export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    }
  },
  computed: {
    fullName: {
      // مُحصلة
      get() {
        return this.firstName + ' ' + this.lastName
      },
      // معيّنة
      set(newValue) {
        // ملاحظة: استخدمنا هنا صيغة التعيين عن طريق التفكيك.
        [this.firstName, this.lastName] = newValue.split(' ')
      }
    }
  }
}

الآن عندما تقوم بتشغيل 'محمد عبيدي' = this.fullName سيتم تشغيل المعيّن وسيتم بالتالي تحديث this.firstName و this.lastName.

vue
<script setup>
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  // مُحصلة
  get() {
    return firstName.value + ' ' + lastName.value
  },
   // معيّنة
  set(newValue) {
  // ملاحظة: استخدمنا هنا صيغة التعيين عن طريق التفكيك.
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>

الآن عندما تقوم بتشغيل 'محمد عبيدي' = this.fullName سيتم تشغيل المعيّن وسيتم بالتالي تحديث this.firstName و this.lastName.

Getting the Previous Value

  • Only supported in 3.4+

In case you need it, you can get the previous value returned by the computed property accessing the first argument of the getter:

js
export default {
  data() {
    return {
      count: 2
    }
  },
  computed: {
    // This computed will return the value of count when it's less or equal to 3.
    // When count is >=4, the last value that fulfilled our condition will be returned
    // instead until count is less or equal to 3
    alwaysSmall(previous) {
      if (this.count <= 3) {
        return this.count
      }

      return previous
    }
  }
}
vue
<script setup>
import { ref, computed } from 'vue'

const count = ref(2)

// This computed will return the value of count when it's less or equal to 3.
// When count is >=4, the last value that fulfilled our condition will be returned
// instead until count is less or equal to 3
const alwaysSmall = computed((previous) => {
  if (count.value <= 3) {
    return count.value
  }

  return previous
})
</script>

In case you're using a writable computed:

js
export default {
  data() {
    return {
      count: 2
    }
  },
  computed: {
    alwaysSmall: {
      get(previous) {
        if (this.count <= 3) {
          return this.count
        }

        return previous;
      },
      set(newValue) {
        this.count = newValue * 2
      }
    }
  }
}
vue
<script setup>
import { ref, computed } from 'vue'

const count = ref(2)

const alwaysSmall = computed({
  get(previous) {
    if (count.value <= 3) {
      return count.value
    }

    return previous
  },
  set(newValue) {
    count.value = newValue * 2
  }
})
</script>

الممارسات الجيّدة

يجب أن تكون الدوال المحصلة خالية من التأثيرات الجانبية

It is important to remember that computed getter functions should only perform pure computation and be free of side effects. For example, don't mutate other state, make async requests, or mutate the DOM inside a computed getter! Think of a computed property as declaratively describing how to derive a value based on other values - its only responsibility should be computing and returning that value. Later in the guide we will discuss how we can perform side effects in reaction to state changes with watchers.

تجنب تعيين القيمة المحسوبة

القيمة المُرجعة من خاصية محسوبة هي حالة مشتقة. فكر فيها كلقطة مؤقتة - كلما تغيرت الحالة المصدرية، سيتم إنشاء لقطة جديدة. بديهيا لا يمكن تعديل لقطة، لذا يجب على أي قيمة محسوبة مُرجعة أن تُقرأ فقط وأن لا يتم تعديلها - بدلاً من ذلك، قم بتحديث الحالة المصدرية التي يعتمد عليها لتشغيل حسابات جديدة.

الخاصيات المحسوبة has loaded