Vue.js: البيانات التفاعلية: ref/reactive
آخر تحديث: 2026-08-26
يُعد نظام التفاعلية في Vue 3 قلبها وروحها — فعندما تقوم بتعديل متغير ما، يتم تحديث كل مكان في الصفحة يستخدم فيه هذا المتغير تلقائيًا. ولهذا السبب، فإن كتابة Vue أسهل بعشر مرات من كتابة jQuery. ويعتمد نظام التفاعلية على واجهتي برمجة تطبيقات أساسيتين: ref() وreactive().
يُستخدم ref لتغليف أي نوع من البيانات (الأرقام، السلاسل، الكائنات)، بينما لا يمكن لـ reactive سوى تغليف الكائنات. ويختلف استخدام كل منهما عن الآخر، وسيساعدك هذا الدرس على فهم الفرق تمامًا.
1. ما ستتعلمه
- جوهر البيانات التفاعلية: اعتراض الوكيل + جمع التبعيات + تشغيل التحديثات
ref()صيغة تغليف أي قيمة،.valueصيغة الوصول إليهاreactive()جهاز التغليف، استجابة تلقائية للعمق- الفروق الخمسة الرئيسية بين «Ref» و«Reactive»، ومتى يستخدم كل منهما
- shallowRef / shallowReactive التفاعلية السطحية
- كيفية استخدام الدوال المساعدة toRef و toRefs
- 5 أخطاء شائعة تؤدي إلى فشل التصميم المتجاوب
2. قصة صفحة عداد سارت الأمور فيها على نحو خاطئ
(1) المشكلة: عندما يتغير العدد، لا يتم تحديث واجهة المستخدم
كتبت أليس أول مكون لها باستخدام Vue 3 من أجل لوحة إدارة موقع التجارة الإلكترونية. وحاولت تنفيذ «عداد مخزون المنتجات»:
// ❌ The "Broken" Version
let count = 0
function increment() {
count++
console.log(count) // 1, 2, 3... It looks like
}
<template>
<p>Stock: {{ count }}</p>
<button @click="increment">+1</button>
</template>
تضغط على الزر 5 مرات. تظهر على شاشة وحدة التحكم الأرقام 1، 2، 3، 4، 5. لكن الصفحة لا تزال تعرض "المخزون: 0".
تنظر مديرة المنتج تشارلي من فوق كتفها:
"أليس، لماذا لا يتم تحديث العدد؟ لقد نقرت 5 مرات. هل تعطل برنامج Vue؟"
(2) حلول Vue التفاعلية: ref() / reactive()
<!-- ✅ Correct Version -->
<template>
<p>Stock: {{ count }}</p>
<button @click="increment">+1</button>
</template>
<script setup>
import { ref } from 'vue'
// Use ref() Wrapper → Vue Knows it should be \"Reactive\"
const count = ref(0)
function increment() {
// In JS Key Points .value
count.value++
// Automatic unwrapping in templates,Not necessary .value
}
</script>
الآن، عند النقر على الزر، يتم تحديث الصفحة على الفور. عندما يتغير المتغير → يتم تحديث DOM تلقائيًا.
(3) الإيرادات
بعد تعلم مبدأ التفاعلية في Vue 3:
- حجم الكود: 30 سطراً من تعليمات jQuery لمعالجة DOM → 5 أسطر من Vue
- معدل الأخطاء: 50% (نسيان تحديث DOM) → 0% (تتولى Vue معالجة الأمر)
- الحمل المعرفي: «ما هي عناصر DOM التي تعتمد على هذا المتغير؟» → «جميعها، تلقائيًا»
3. مبادئ التفاعلية: الوكيل + تجميع التبعيات
(1) عملية الاستجابة المكونة من 3 خطوات
sequenceDiagram
participant T as Template
participant C as Component
participant R as Reactive<br/>(Proxy)
participant D as Data
T->>C: 1. Render (Read count)
C->>R: 2. get count
R->>D: 3. Read 0
R->>R: 4. Track: Who's using it? count?
R-->>C: 5. Return 0
C-->>T: 6. Display "Stock: 0"
Note over D,T: The user clicks the button
D->>R: 7. set count = 1
R->>R: 8. Trigger: Notice to All Users count the place
R->>C: 9. Re-render
C-->>T: 10. Display "Stock: 1"
(2) النقاط الرئيسية
- اعتراض البروكسي: يستخدم Vue 3 بروكسيات ES6 لاعتراض عمليات get/set
- مجموعة التبعيات: تسجل "من استخدم هذه البيانات" (التتبع) عند عرض القالب
- تشغيل التحديث: إخطار «جميع المواقع ذات الصلة» بإعادة العرض عند تغير البيانات (المحفز)
(3) Vue 2 مقابل Vue 3: مقارنة بين التفاعلية
| البعد | المنظر 2 | المنظر 3 |
|---|---|---|
| واجهة برمجة التطبيقات الأساسية | Object.defineProperty |
ES6 Proxy |
| مراقبو المصفوفات | يتطلب معالجة خاصة | دعم أصلي |
| الخريطة/ضبط الساعة | غير مدعوم | مدعوم |
| إضافة عقار جديد | يتطلب Vue.set() |
الرد التلقائي |
| الأداء | المتوسط | أفضل (الوكيل البطيء) |
4. ref(): يلف أي قيمة
(1) الاستخدام الأساسي
import { ref } from 'vue'
// Packaging Numbers
const count = ref(0)
// Packaging String
const name = ref('Alice')
// Boolean Packaging
const isVip = ref(false)
// Packaging nulo
const data = ref(nulo)
// Packaging Array
const item = ref([1, 2, 3])
// Items to Be Packaged
const user = ref({ name: 'Bob', age: 25 })
(2) الوصول إلى القيم: استخدم .value في JavaScript؛ حيث يقوم القالب بفك ضغطها تلقائيًا
<template>
<!-- Using variable names directly in templates(Automatic Unpacking) -->
<p>{{ count }}</p> <!-- 0 -->
<p>{{ name }}</p> <!-- Alice -->
<p>{{ user.name }}</p> <!-- Bob -->
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const name = ref('Alice')
const user = ref({ name: 'Bob', age: 25 })
function logCount() {
// In JS Must use .value
console.log(count.value) // 0
console.log(name.value) // Alice
console.log(user.value.name) // Bob
}
</script>
(3) الطبيعة «ثنائية الاتجاه» لـ ref
import { ref } from 'vue'
const count = ref(0)
// Read
console.log(count.value) // 0
// Edit
count.value = 1
console.log(count.value) // 1
// Modify Nested Properties(ref When wrapping an object)
const user = ref({ name: 'Bob' })
user.value.name = 'Alice' // ✅ Responsive
user.value = { name: 'Charlie' } // ✅ The entire replacement also responds
5. reactive(): كائن غلاف (التفاعلية العميقة)
(1) الاستخدام الأساسي
import { reactive } from 'vue'
// Only objects can be passed
const state = reactive({
count: 0,
user: { name: 'Alice', age: 25 },
item: [1, 2, 3]
})
// No access required .value
console.log(state.count) // 0
console.log(state.user.name) // Alice
// Edit Auto-Reply
state.count = 1
state.user.name = 'Bob'
state.item.push(4)
(2) رد مفصل
const state = reactive({
level1: {
level2: {
level3: {
value: 'deep'
}
}
}
})
// Changes made at any level are automatically reflected
state.level1.level2.level3.value = 'updated' // ✅ Trigger an update
(3) حدود البرمجة التفاعلية
// ❌ You cannot directly replace the entire object.(No longer responsive)
let state = reactive({ count: 0 })
state = reactive({ count: 1 }) // Wrong! state variable itself changed, but template uses old reference.
// ❌ Deconstruction results in the loss of responsiveness
const state = reactive({ count: 0, name: 'Alice' })
const { count, name } = state // count/name Convert to a regular variable,Not responding
// ✅ Destructuring Reactivity: use toRefs
import { toRefs } from 'vue'
const { count, name } = toRefs(state) // count/name.value Stay Responsive
6. الفروق الخمس الرئيسية بين ref و reactive
| الجانب | المرجع | التفاعلية |
|---|---|---|
| الأنواع القابلة للتغليف | أي قيمة (عدد، سلسلة، كائن، array) | الكائنات فقط (كائن/array) |
| طريقة الوصول | .value (JS) |
الوصول المباشر (JS) |
| الوصول إلى القوالب | الفك التلقائي (بدون .value) | الوصول المباشر |
| استبدال الكل | ref.value = {...} ✅ يستجيب |
reactive({...}) = ... ❌ لا يستجيب |
| التفكيك | يتم الاحتفاظ بالردود (toRefs) | تُفقد الردود (toRefs مطلوب) |
(4) توصيات الاختيار
| السيناريو | التوصية |
|---|---|
| الأنواع الأولية (عدد/سلسلة/منطقية) | ref() |
| كائن واحد متجاوب | يُقبل إما ref({}) أو reactive({}) |
| نظام يتألف من عدة مجالات مترابطة | reactive({}) (مثل Vuex) |
| يجب استبداله بالكامل | ref({}) (أكثر مرونة) |
| تمرير الدعائم / الإرسال باستخدام | ref() (حدود واضحة) |
7. التفاعلية السطحية: shallowRef / shallowReactive
(1) متى ينبغي استخدام الردود السطحية؟
عندما يكون لديك كائن كبير، ولكنك مهتم فقط بالتغييرات التي تطرأ على الحقول ذات المستوى الأعلى، فإن الاستجابة السطحية يمكن أن تحسّن الأداء.
import { shallowRef, shallowReactive, triggerRef } from 'vue'
// shallowRef:Follow Only .value Replace All,Do not track internal properties
const bigData = shallowRef({ item: [...50000 item] })
bigData.value.item.push('new') // ❌ Do not trigger an update
bigData.value = { item: [newItems] } // ✅ Trigger an update(Replace All)
// shallowReactive:Track only top-level properties,No in-depth response
const state = shallowReactive({
user: { name: 'Alice' },
settings: { theme: 'dark' }
})
state.user.name = 'Bob' // ❌ Does not trigger
state.user = { name: 'Bob' } // ✅ Trigger(Top-Level Property Replacement)
(2) فرض التحديث
import { triggerRef } from 'vue'
const bigData = shallowRef({ items: [...] })
bigData.value.items.push('new') // Do not respond by default
triggerRef(bigData) // Manually Trigger an Update
السيناريوهات النموذجية: الجداول الكبيرة (100,000 صف)، والقوائم الكبيرة، والحالات التي يتم فيها تعديل الخصائص الداخلية بشكل متكرر، لكن الstatus الإجمالية هي المهمة فقط.
8. toRef / toRefs: التفكيك مع الحفاظ على التفاعلية
(1) toRefs: تفكيك كائن تفاعلي
import { reactive, toRefs } from 'vue'
const state = reactive({
count: 0,
name: 'Alice'
})
// ❌ General Deconstruction:Missing Response
const { count, name } = state
// ✅ toRefs Deconstruction:Stay Responsive
const { count, name } = toRefs(state)
// Used in templates
// <p>{{ count }}</p> <!-- Still responding -->
(2) toRef: تحويل قيمة عادية إلى مرجع
import { toRef, ref } from 'vue'
// Scene: Select one field from props
const props = defineProps({ count: Number })
// ❌ Directly Deconstructing Missing Responses
const { count } = props // count It's ordinary numbervalue
// ✅ toRef Packaging
const count = toRef(props, 'count') // count is ref(props.count)
// Usage
count.value++ // Responsive Changes props
9. 5 عقبات في التصميم المتجاوب
(1) المأزق الأول: الاستبدال المباشر لكائن تفاعلي
let state = reactive({ count: 0 })
// ❌ state The entire variable has been changed,But the template uses the old version state
state = reactive({ count: 1 })
// ✅ Edit Properties
state.count = 1
(2) المأزق الثاني: تفكيك رد الفعل وفقدان الاستجابة
const state = reactive({ count: 0 })
// ❌ count It is a regular variable
const { count } = state
// ✅ Use toRefs
const { count } = toRefs(state)
(3) المأزق الثالث: التعيين المباشر إلى المصفوفات
const item = ref([1, 2, 3])
// ❌ shallowRef يتتبع فقط استبدال .value، وليس التغييرات الداخلية
items.value.push(4) // لا يُطلق إعادة العرض
// ✅ استبدل المصفوفة بأكملها
items.value = [...items.value, 4]
// ملاحظة: مع ref() العادي، push تفاعلي (ref يلف المصفوفة في reactive proxy)
const list = ref([1, 2, 3])
list.value.push(4) // ✅ يُطلق إعادة العرض في Vue 3
(4) المأزق الرابع: التعديل المباشر لمحتويات كائن محاط بـ ref
// It generally does not expire.,However, there are edge cases
const user = ref({ name: 'Alice' })
user.value.name = 'Bob' // ✅ Response(ref Wrapped objects are automatically unwrapped)
(5) المأزق الخامس: الخلط بين النوعين ref وreactive
// ❌ Type error:reactive Cannot wrap the original value
const count = reactive(0) // Error: value cannot be made reactive
// ✅ Use ref
const count = ref(0)
10. مثال كامل: عربة التسوق المتوافقة مع جميع الأحجام
▶ مثال: 1. الاستخدام الأساسي لـ ref
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">+1</button>
<button @click="reset">Reset</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++ // JS Must be used in .value
}
function reset() {
count.value = 0
}
</script>
▶ مثال: 2. كائنات التغليف التفاعلية
<template>
<div>
<p>User: {{ state.user.name }}</p>
<p>Age: {{ state.user.age }}</p>
<button @click="birthday">+1 Year</button>
</div>
</template>
<script setup>
import { reactive } from 'vue'
const state = reactive({
user: { name: 'Alice', age: 25 }
})
function birthday() {
state.user.age++ // Edit directly,Automatic Response
}
</script>
▶ مثال: 3. مقارنة بين ref و reactive
import { ref, reactive } from 'vue'
// ref Style
const userRef = ref({ name: 'Alice', age: 25 })
// Edit:
userRef.value.name = 'Bob' // ✅ Response
userRef.value = { name: 'Charlie', age: 30 } // ✅ Response
// reactive Style
const userReactive = reactive({ name: 'Alice', age: 25 })
// Edit:
userReactive.name = 'Bob' // ✅ Response
// userReactive = reactive({...}) // ❌ Failure
▶ مثال: 4. toRefs — تفكيك المتغيرات مع الحفاظ على الاستجابة
<template>
<div>
<p>{{ count }} - {{ name }}</p>
<button @click="increment">+1</button>
</div>
</template>
<script setup>
import { reactive, toRefs } from 'vue'
const state = reactive({ count: 0, name: 'Alice' })
const { count, name } = toRefs(state) // Stay Responsive
function increment() {
count.value++ // count is ref, So use .value
}
</script>
▶ مثال: 5. استجابة سطحية + triggerRef
import { shallowRef, triggerRef } from 'vue'
// Large Table 100,000 rows → use shallowRef
const bigTable = shallowRef({
rows: Array.from({ length: 100000 }, (_, i) => ({ id: i, name: `Row ${i}` }))
})
// Replace All(Highly efficient)
function reloadData(newRows) {
bigTable.value = { rows: newRows }
}
// Modify Internal Properties(Not responding,Must be done manually trigger)
function updateRow(id, newName) {
const row = bigTable.value.rows.find(r => r.id === id)
if (row) {
row.name = newName
triggerRef(bigTable) // Manually Trigger an Update
}
}
▶ مثال: 6. مرجع سريع لـ 5 أخطاء شائعة
| الخطأ | الأعراض | الحل |
|---|---|---|
let state = reactive({...}); state = {...} |
استبدال الstatus بالكامل؛ لم يتم تحديث القالب | تعديل الخاصية state.x = 1 |
const { count } = state تفكيك |
تحويل count إلى متغير عادي |
استخدام toRefs(state) |
reactive(0) قيمة العبوة الأصلية |
خطأ | استخدم ref() للقيمة الأصلية |
count.value++ في القالب |
خطأ | القالب مباشرةً {{ count }} |
array[index] = x مع shallowRef |
لا يُطلق التحديث (shallow يتتبع .value فقط) | استخدم splice أو استبدل المصفوفة بأكملها |
❓ أسئلة شائعة
ref أم reactive؟ref حصريًّا (السبب: ref يعمل مع أي نوع ويوفر استبدالًا وتمريرًا أكثر وضوحًا للخصائص/الدوال بشكل عام). reactive مناسب للحالات التي «تتكون فيها الstatus من عدة حقول مرتبطة» (مثل Vuex). يُنصح المبتدئون بتعلم ref أولاً واستخدام reactive بمجرد أن يصبحوا أكثر إتقانًا..value بينما لا يحتاج القالب إلى ذلك؟.value لإخبار محرك جافا سكريبت صراحةً بأن «هذه قيمة مغلفة بمرجع؛ ويجب جعلها تفاعلية». تقوم Vue بتحليل القوالب بنفسها؛ فهي تعرف أي العناصر تمثل مراجع وتقوم بإزالة التغليف عنها تلقائيًا. باختصار: يُعد .value مطلوبًا في JavaScript (لتجنب الغموض)، بينما تقوم القوالب بإزالة تغليف المراجع تلقائيًا (للسهولة)..value.x للوصول إلى خصائصه؟ref({name: 'Alice'})، يستخدم القالب {{ user.name }} مباشرةً، بينما تستخدم JavaScript user.value.name. تتولى Vue تلقائيًا عملية الوصول إلى الخصائص الداخلية للكائن المغلف بواسطة ref.toRefs(). بعد const { count, name } = toRefs(state)، تصبح count وname مراجع؛ بينما تظل كل من {{ count }} في القالب وcount.value في جافا سكريبت تفاعليتين.ref هي مرجع عميق (تؤدي التغييرات في الخصائص الداخلية إلى إحداث تغييرات أيضًا)، بينما shallowRef تتتبع فقط استبدال .value ككل (ولا تتتبع التغييرات الداخلية). عندما تكون مجموعة البيانات كبيرة (أكثر من 100,000 سجل) ولا يهمك سوى الاستبدال الكلي، فإن استخدام shallowRef يوفر أداءً أفضل.toRefs؛ (3) تغليف قيمة بدائية في ref غير تفاعلي (استخدم reactive بدلاً من ذلك أو احتفظ بـ ref)؛ (4) إنشاء ref خارج setup (مما يؤدي إلى فقدان التفاعلية)؛ (5) التعيين المباشر إلى array (استخدم splice بدلاً من ذلك).reactive أن يلف Map وSet؟reactive في Vue 3، الذي تم تنفيذه باستخدام Proxy، Map وSet وWeakMap وWeakSet. لا يدعم Vue 2 ذلك.📖 ملخص
- جوهر نظام التفاعلية في Vue 3: اعتراض الوكيل + تتبع التبعيات + تشغيل التحديثات
ref()يغلف أي قيمة؛ يتطلب.valueللوصول إليه من JavaScript؛ يتم فك تغليفه تلقائيًا بواسطة القالبreactive()كائن مغلف (كائن/array)، استجابة عميقة، لا يوجد .value- الاختيار بين
refوreactive: يُعدrefمتعدد الاستخدامات ويُوصى به؛ أماreactiveفهو مناسب لـ«status متعددة المجالات» - 5 عيوب رئيسية: استبدال الكائنات التفاعلية / فقدان الاستجابات أثناء عملية تفكيك الكائنات / استخدام «reactive» مع القيم الأولية / التعيين المباشر للمصفوفات / الخلط بين الأنواع
- تُستخدم shallowRef و shallowReactive لتحسين الأداء عند التعامل مع الكائنات الكبيرة
- تحافظ toRefs على القيمة المستردة عند تفكيك البنية؛ بينما تقوم toRef بتحويل القيمة العادية إلى مرجع
📝 تمارين
-
أسئلة أساسية (مستوى الصعوبة: ⭐)
اكتب عدادًا بسيطًا لعدد العناصر في سلة التسوق باستخدام
ref:- القيمة الأولية 1
- زر "+": إضافة 1
- يؤدي الضغط على الزر «-» إلى خفض القيمة بمقدار 1
- تعطيل زر "-" عندما تكون الكمية ≤ 0 (:تعطيل ديناميكي)
- تعطيل زر "+" عندما تكون الكمية 99 أو أكثر
-
مسائل متقدمة (مستوى الصعوبة: ⭐⭐)
اكتب نموذجًا لتعديل ملف تعريف المستخدم باستخدام Reactive:
- يحتوي حقل «الstatus» على ثلاثة حقول:
nameوemailوage - الربط التفاعلي لثلاثة حقول إدخال (باستخدام حدث @input؛ وسنتناول v-model في الدرس التالي)
- يعيد زر «إعادة الضبط» القيم الافتراضية
- يقوم زر «إرسال» بطباعة الstatus الكاملة على وحدة التحكم
- يحتوي حقل «الstatus» على ثلاثة حقول:
-
مسألة التحدي (مستوى الصعوبة: ⭐⭐⭐)
تنفيذ تطبيق مهام متكامل (طبقة البيانات):
- قم بتغليف الarray
todosبـreactive - كل مهمة تتضمن
{ id, text, done } - الطرق المتوفرة: addTodo(text) / toggleTodo(id) / removeTodo(id) / clearDone()
- توفير الخصائص المحسوبة التالية: activeCount (عدد العناصر غير المكتملة) و doneCount (عدد العناصر المكتملة)
- عرض قائمة المهام الكاملة + العدد في القالب
البيانات الأولية:
{ id: 1, text: 'Learn Vue 3', done: false } - قم بتغليف الarray