Vue.js: الخصائص المحسوبة
آخر تحديث: 2026-08-26
الخصائص المحسوبة (computed) هي خصائص خاصة في Vue 3 تُعاد حسابها تلقائيًا استنادًا إلى البيانات التفاعلية. وتكمن قيمتها الأساسية في التخزين المؤقت — حيث لا تُعاد حسابها إلا عند تغير البيانات التي تعتمد عليها؛ وإلا فإنها تعيد ببساطة القيمة السابقة. ويُعد فهم الخصائص المحسوبة أمرًا أساسيًّا لكتابة تطبيقات Vue عالية الأداء.
تتمحور الخصائص المحسوبة حول ثلاث ميزات رئيسية: التتبع التلقائي للتبعيات + تخزين النتائج مؤقتًا + التفاعلية. سيساعدك هذا الدرس على إتقان حالات الاستخدام الخمس لهذه الخصائص وتجنب العقبات الثلاث المرتبطة بها.
1. ما ستتعلمه
computed()القواعد النحوية الأساسية وقيم الإرجاع- الاختلافات الرئيسية بين الخصائص المحسوبة والطرق (التخزين المؤقت)
- الخصائص المحسوبة ثنائية الاتجاه من نوع «Getter/Setter»
- استدعاءات متسلسلة للخصائص المحسوبة (يتم حسابها داخل خصائص محسوبة أخرى)
- الاستخدامات المختلفة لكلمة «computed» في القوالب والأساليب
- 3 عيوب شائعة (سوء الاستخدام، التبعيات الدائرية، مشكلات الأداء)
2. مشكلة في الأداء تتعلق بالسعر الإجمالي لعربة التسوق
(1) المشكلة: يتم إعادة حساب 100 منتج مع كل نقرة
قامت أليس بإنشاء وظيفة حساب المجموع النهائي لعربة التسوق في لوحة إدارة موقع التجارة الإلكترونية الخاص بها:
// ❌ The "Broken" Version: Use methods
function cartTotal(items) {
console.log('Calculating...') // Print on every call
return items.reduce((sum, item) => sum + item.price * item.quantity, 0)
}
<template>
<div>
<p>Total: ${{ cartTotal(items) }}</p>
<p>Tax: ${{ cartTotal(items) * 0.08 }}</p>
<p>Grand Total: ${{ cartTotal(items) * 1.08 }}</p>
<button @click="showDate = new Date()">Update Time</button>
</div>
</template>
الأداء سيئ للغاية:
- عدد مرات تحميل الصفحة: يتم إجراء الحساب مرة واحدة ✅
- عملية حسابية واحدة: 3 مرات (الإجمالي، الضريبة، الإجمالي الكلي) = 300 عملية حسابية للبنود
- ينقر المستخدم على «تحديث الوقت» (بغض النظر عن العناصر): 0 تغيير، لكن يستمر الحساب في التشغيل 3 مرات = 300 عملية حسابية للعناصر
تُظهر أداة مراقبة الأداء ما يلي:
"يتم استدعاء دالة cartTotal 5,000 مرة عند كل تحميل للصفحة. 100 عنصر × 5,000 = 500,000 عملية حسابية لكل عنصر على حدة. تباطؤ لمدة 3 ثوانٍ."
(2) الحل المستند إلى الحسابات في Vue: التخزين المؤقت + تتبع التبعيات
import { ref, computed } from 'vue'
const item = ref([
{ price: 100, quantity: 2 },
{ price: 50, quantity: 1 }
])
// ✅ computed:Rely solely on item,item If it hasn't changed, don't recalculate it.
const cartTotal = computed(() => {
console.log('Calculating...') // Only at item Print when changes occur
return item.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
})
// computed Auto-Processing 3 For use in: Count only 1 time
const tax = computed(() => cartTotal.value * 0.08)
const grandTotal = computed(() => cartTotal.value * 1.08)
(3) الإيرادات
بعد التحويل إلى الصيغة الحسابية:
- استدعاءات الدوال: 5,000 → 100 (-98%)
- الوقت المستغرق للوصول إلى التفاعل: 3 ثوانٍ → 80 مللي ثانية (-97%)
- التقطع أثناء التمرير: 5/ثانية → 0 (-100%)
- استخدام وحدة المعالجة المركزية: 80% → 5% (-94%)
3. الاستخدام الأساسي لوظيفة computed()
(1) القواعد النحوية القياسية
import { ref, computed } from 'vue'
const count = ref(0)
// Basic Computational Properties
const doubleCount = computed(() => {
return count.value * 2
})
// Abbreviation
const doubleCount = computed(() => count.value * 2)
// Usage
console.log(doubleCount.value) // 0
count.value = 5
console.log(doubleCount.value) // 10
(2) الاستخدام في القوالب
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double: {{ doubleCount }}</p>
<button @click="count++">+1</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
</script>
في القالب، {{ doubleCount }} ليس مطلوبًا .value (يتم تفريغه تلقائيًا).
(3) 5 ميزات رئيسية
graph TB
subgraph ComputedKey Features
A[Automatic Dependency Tracking]
B[Cache Results]
C[Responsive]
D[Lazy Evaluation]
E[Chainable calls]
end
style A fill:#42b883,color:#fff
style B fill:#42b883,color:#fff
style C fill:#42b883,color:#fff
style D fill:#42b883,color:#fff
style E fill:#42b883,color:#fff
| الميزة | الوصف |
|---|---|
| التتبع التلقائي للتبعيات | يكتشف تلقائيًا البيانات التفاعلية المستخدمة داخل أجسام الدوال |
| النتائج المخزنة مؤقتًا | تُحسب عمليات الوصول المتعددة كعملية واحدة فقط إذا بقيت التبعيات دون تغيير |
| متجاوب | يُعاد حسابه تلقائيًا عند تغير التبعيات |
| التقييم المتأخر | لا يتم تقييمه ما لم يتم الوصول إليه (يوفر الأداء) |
| قابلة للتسلسل | قد تعتمد القيمة المحسوبة على خصائص محسوبة أخرى |
4. القيم المحسوبة مقابل الطرق: 5 اختلافات رئيسية
| الجانب | المحسوب | الطرق |
|---|---|---|
| التخزين المؤقت | ✅ لا تتم إعادة الحساب إذا بقيت التبعيات دون تغيير | ❌ تتم إعادة الحساب عند كل استدعاء |
| متجاوب | ✅ يتتبع التبعيات تلقائيًا | ❌ لا يتتبع التبعيات (يتطلب تمرير المعلمات يدويًّا) |
| الوصول إلى القالب | {{ doubleCount }} |
{{ doubleCount() }} |
| المعلمات | ❌ غير مدعومة | ✅ تقبل أي معلمات |
| حالات الاستخدام | القيم المشتقة البسيطة | المنطق المعقد، معالجة الأحداث |
(4) 5 سيناريوهات للاختيار من بينها
| السيناريو | باستخدام القيم المحسوبة | باستخدام الطرق |
|---|---|---|
| مضاعفة الرقم المعروض | ✅ | ❌ |
| حساب المبلغ الإجمالي لعربة التسوق | ✅ | ❌ |
| معالجة النقر على الزر | ❌ | ✅ |
| إرسال طلب API | ❌ | ✅ |
| تنسيق التاريخ (بدون تبعيات) | ❌ | ✅ |
| قائمة التصفية (حسب البيانات) | ✅ | ❌ |
| التحقق من صحة النموذج | ❌ | ✅ |
5. Getter/Setter: الخصائص المحسوبة ثنائية الاتجاه
(1) بشكل افتراضي، لا يوجد سوى دالة الحصول
const firstName = ref('Alice')
const lastName = ref('Smith')
// There is only one getter
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
console.log(fullName.value) // Alice Smith
// fullName.value = 'Bob Lee' // ❌ Warning:Calculated properties are read-only.
(2) دالات الحصول + دالات التعيين الكاملة
const firstName = ref('Alice')
const lastName = ref('Smith')
const fullName = computed({
// Read:Put it together
get() {
return `${firstName.value} ${lastName.value}`
},
// Write:Unpack
set(newValue) {
const parts = newValue.split(' ')
firstName.value = parts[0]
lastName.value = parts[1] || ''
}
})
// ✅ Read and write access is now available
fullName.value = 'Bob Lee'
console.log(firstName.value) // Bob
console.log(lastName.value) // Lee
(3) الربط ثنائي الاتجاه في القوالب
<template>
<div>
<input v-model="fullName">
<p>First: {{ firstName }}</p>
<p>Last: {{ lastName }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('Alice')
const lastName = ref('Smith')
const fullName = computed({
get: () => `${firstName.value} ${lastName.value}`,
set: (val) => {
const parts = val.split(' ')
firstName.value = parts[0]
lastName.value = parts[1] || ''
}
})
</script>
6. المكالمات المتسلسلة للخصائص المحسوبة
(1) حساب متداخل
const items = ref([
{ price: 100, quantity: 2 },
{ price: 50, quantity: 3 }
])
// First Floor:Total Original Price
const subtotal = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
)
// Second Floor: Tax (Based on subtotal)
const tax = computed(() => subtotal.value * 0.08)
// Third Floor: Discount (Based on subtotal)
const discount = computed(() => subtotal.value > 200 ? 20 : 0)
// Fourth Floor:Final Total Price
const total = computed(() => subtotal.value + tax.value - discount.value)
console.log(total.value) // 350 * 1.08 - 20 = 358
(2) آلية cache محسوبة متسلسلة
graph TB
A[items change] --> B[subtotal Recalculate]
B --> C[tax Recalculate]
B --> D[discount Recalculate]
C --> E[total Recalculate]
D --> E
style A fill:#42b883
style B fill:#42b883
style E fill:#42b883
لن يتم إعادة حساب سوى القيم المحسوبة التي تعتمد على السلسلة؛ أما القيم غير المستخدمة فلن يتم إعادة حسابها.
7. مثال كامل: السعر الإجمالي لعربة التسوق في موقع التجارة الإلكترونية
▶ مثال: 1. الحسابات الأساسية
<template>
<div>
<p>Subtotal: ${{ subtotal }}</p>
<p>Tax (8%): ${{ tax }}</p>
<p>Discount: -${{ discount }}</p>
<p><strong>Total: ${{ total }}</strong></p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const items = ref([
{ name: 'iPhone', price: 999, quantity: 1 },
{ name: 'Case', price: 50, quantity: 2 }
])
const subtotal = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
)
const tax = computed(() => subtotal.value * 0.08)
const discount = computed(() => subtotal.value > 1000 ? 50 : 0)
const total = computed(() => subtotal.value + tax.value - discount.value)
</script>
▶ مثال: 2. دالات الاسترجاع + دالات التعيين
<template>
<div>
<input v-model="fullName" placeholder="First Last">
<p>First: {{ firstName }}</p>
<p>Last: {{ lastName }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('Alice')
const lastName = ref('Smith')
const fullName = computed({
get: () => `${firstName.value} ${lastName.value}`,
set: (val) => {
const parts = val.split(' ')
firstName.value = parts[0] || ''
lastName.value = parts[1] || ''
}
})
</script>
▶ مثال: 3. استدعاءات السلسلة المحسوبة
import { ref, computed } from 'vue'
const score = ref(85)
// First Floor:Base Score
const baseScore = computed(() => score.value)
// Second Floor:Bonus Points(Base Score × Difficulty Level)
const difficultyBonus = computed(() => {
if (baseScore.value >= 90) return 10
if (baseScore.value >= 80) return 5
return 0
})
// Third Floor:Level
const grade = computed(() => {
const total = baseScore.value + difficultyBonus.value
if (total >= 95) return 'A+'
if (total >= 90) return 'A'
if (total >= 80) return 'B'
return 'C'
})
▶ مثال: 4. مقارنة بين القيم المحسوبة والأساليب
// ❌ methods:Recalculate on every call
function getTotal() {
console.log('Calculated!') // If used multiple times in the template, it will be printed multiple times.
return items.value.reduce((sum, i) => sum + i.price * i.quantity, 0)
}
// ✅ computed: Count only 1 time
const total = computed(() => {
console.log('Calculated!') // Print Only 1 time
return items.value.reduce((sum, i) => sum + i.price * i.quantity, 0)
})
▶ مثال: 5. خمسة سيناريوهات لمقارنة الأداء
| المشهد | عدد الطرق التي تم استدعاؤها | عدد المكالمات المحسوبة |
|---|---|---|
| استُخدم مرة واحدة في القالب | 1 | 1 |
| استُخدم 3 مرات في القالب | 3 | 1 |
| استُخدم 10 مرات في القالب | 10 | 1 |
| يتم تحديث المكون الأصلي لكن العناصر تظل دون تغيير | تم استدعاؤها | لم تتم إعادة حسابها |
| تغييرات التبعيات | الاسترجاع | إعادة الحساب |
▶ مثال: 6. 5 أخطاء شائعة
| الخطأ | الأعراض | الحل |
|---|---|---|
| استبدال «المُحسب» بـ«الأساليب» | ضعف الأداء عند الاستخدام المتكرر | التحول إلى «المُحسب» |
تم تمرير المعلمات بشكل غير صحيح إلى computed |
خطأ | لا يدعم computed المعلمات؛ استخدم methods بدلاً من ذلك |
| مُعيّن: اكتب كودك الخاص | حلقة لا نهائية | مُعيّن: قم بتعديل التبعية؛ لا تستخدم fullName.value = ... |
| التبعية الدائرية A→B→A | حلقة لا نهائية | إزالة إحدى التبعيات |
| منطق معقد مكتوب في الخصائص المحسوبة | يصعب تصحيح الأخطاء | قم باستخراجه إلى دالة؛ واجعل الخاصية المحسوبة تستدعي هذه الدالة |
❓ أسئلة شائعة
computed أم methods؟computed كلما أمكن ذلك (التخزين المؤقت + التتبع التلقائي). استخدم methods فقط في الحالات التالية: (1) عندما تحتاج إلى تمرير معلمات؛ (2) عندما لا تعتمد على البيانات التفاعلية؛ (3) لمعالجات الأحداث (@click، إلخ).computed قبول معلمات؟computed أن تحتوي إلا على 0 معلمة. إذا كنت بحاجة إلى تمرير معلمات، فاستخدم methods بدلاً من ذلك: getDouble(n) { return n * 2 }، أو اجعل computed تُرجع دالة: computed(() => (n) => n * 2).computed من خارج setup؟computed كائن ref، والذي يمكن تمريره إلى مكونات أو دوال أخرى. ومع ذلك، للحفاظ على التفاعلية، يُنصح باستخدام computed كـ«مرجع مشتق».computed وwatch؟computed هي «قيمة مشتقة» (حساب قيمة جديدة بناءً على البيانات)، بينما watch هي «مراقب» (تنفيذ الآثار الجانبية عند تغير البيانات). computed مناسب للحسابات البحتة، بينما watch مناسب لتقديم الطلبات، وتعيين الstatus، وما إلى ذلك.computed على methods؟methods الدالة عند كل استدعاء، بينما لا يُنفذ computed إلا عندما تتغير تبعياته. في القالب، يؤدي استخدام computed ثلاث مرات إلى عملية حسابية واحدة فقط؛ بينما يؤدي استخدام methods إلى ثلاث عمليات حسابية.fullName بتحديث firstName وlastName، وليس fullName.value = ....computed غير متزامنة؟computed متزامنة؛ استخدم watch أو watchEffect للعمليات غير المتزامنة. إذا كنت بحاجة إلى «خصائص محسوبة غير متزامنة»، فاستخدم مزيجًا من data() { return { result: null } } وwatch + onMounted.📖 ملخص
computedهو «مرجع مشتق» يُعاد حسابه تلقائيًا استنادًا إلى البيانات التفاعلية. ويتميز بخمس ميزات رئيسية: التتبع التلقائي، والتخزين المؤقت، والتفاعلية، والتقييم المؤجل، وقابلية التسلسل.- لا يتطلب
{{ doubleCount }}في القالب استخدام .value (التفريغ التلقائي)؛doubleCount.valueفي JS - القيم المحسوبة مقابل الطرق: يتم تخزين القيم المحسوبة مؤقتًا (للأداء)، أما الطرق فلا يتم تخزينها مؤقتًا ولكنها تقبل المعلمات
- الخصائص المحسوبة ثنائية الاتجاه (Getter/Setter): قابلة للقراءة والكتابة؛ حيث تقوم دالة التعيين (setter) بتحديث التبعيات دون تحديث نفسها
- المكالمات المتسلسلة في الخصائص المحسوبة: يمكن أن تعتمد الخاصية المحسوبة على خصائص محسوبة أخرى؛ ولا يتم إعادة حساب الخصائص المحسوبة غير المستخدمة.
- 3 عيوب: التبعيات الدائرية، وسوء استخدام الطرق، والمنطق المعقد الذي يصعب تصحيحه
- الأداء: تُستخدم الخاصية
computedفي ثلاثة مواضع في القالب، لكنها تُحسب مرة واحدة فقط، في حين تُحسب الخاصيةmethodsثلاث مرات.
📝 تمارين
-
أسئلة أساسية (مستوى الصعوبة: ⭐)
اكتب خاصية محسوبة
wordCountتحسب عدد الكلمات في نص ما (باستخدامsplitلتقسيم النص حسب المسافات).البيانات:
const text = ref('Hello Vue 3 from Alice')يجب أن تكون النتيجة 4. -
مسائل متقدمة (مستوى الصعوبة: ⭐⭐)
اكتب معادلة لحساب إجمالي سلة التسوق تتضمن 3 خصائص محسوبة:
subtotal: مجموع السعر والكمية لجميع الأصنافdiscount: وفر 10 عند الطلبات التي تبلغ قيمتها 100 أو أكثر؛ وفر 30 عند الطلبات التي تبلغ قيمتها 200 أو أكثرtotal: المجموع الفرعي - الخصم- 3 قطع: آيفون 999 دولارًا × 1، غطاء 50 دولارًا × 2، شاحن 30 دولارًا × 1
-
مسألة التحدي (مستوى الصعوبة: ⭐⭐⭐)
تنفيذ «نظام إجمالي سلة التسوق» الكامل:
- 5 منتجات، كل منها مع الاسم والسعر والكمية
- المجموع الفرعي / الضريبة (8٪) / الخصم (20 عند شراء 200 أو أكثر) / المجموع — 4 قيم محسوبة
- استخدم دالات الاسترجاع والتعيين للخاصية المحسوبة
fullName؛ وفي دالة التعيين، قم بتحديثfirstNameوlastNameبناءً على المدخلات. - اختبار تتبع التبعيات لخمس خصائص محسوبة (أي منها يتم إعادة حسابها بعد تعديل أي منتج)
- استخدم Mermaid لرسم مخطط التبعية للخصائص المحسوبة