Vue.js: التفاعل بعمق

آخر تحديث: 2026-08-26

إن فهم المبادئ الكامنة وراء التفاعلية في Vue 3 سيمكّنك من كتابة كود أكثر كفاءة وتصحيح مشكلات التفاعلية المعقدة. يتعمق هذا الدرس في التنفيذ الأساسي لنظام التفاعلية في Vue 3: معترضات البروكسي، وحل التبعيات، ومحفزات التحديث، والآليات الداخلية لـ refs، والخصائص المحسوبة، وwatches.

يحل Vue 3 محل Object.defineProperty في Vue 2 باستخدام Proxy في ES6، مما يوفر دعمًا أصليًّا للمصفوفات و«Maps» و«Sets»، ويحقق زيادة في الأداء بمقدار الضعف. وبمجرد فهم كيفية عمله، يمكنك تجنب خمسة أخطاء شائعة تتعلق بالبيانات التفاعلية.

1. ما ستتعلمه



2. حادثة «تجميد» تتعلق بجدول مكون من 100,000 صف

(1) المشكلة: تؤدي قائمة تضم 100,000 عنصر إلى تجميد الصفحة بعد تضمينها في tag ref

قامت أليس بتحميل جدول منتجات مكون من 10,000 صفٍّ للمسؤول:

JS
// ❌ The "Broken" Version:deep reactive Processing 10 Ten Thousand Lines
import { ref } from 'vue'

const products = ref([
  { id: 1, name: 'Product 1', price: 99, ... },
  // ... 10,000 items ...
])

// Vue For each property,All nested objects are created Proxy
// Memory Usage:100MB+
// First Render: 3s
// Scroll:10 FPS(Lag)

يُظهر شاشة مراقبة الأداء ما يلي:

"10,000 صف × 4 خصائص متداخلة × عبء الوكيل = 40,000 وكيل. كل عملية وصول إلى خاصية تؤدي إلى تشغيل عملية التتبع. الذاكرة: 100 ميغابايت. معدل الإطارات في الثانية: 10."

(2) حل «shallowRef» و«markRaw» في Vue

JS
import { shallowRef, markRaw } from 'vue'

// ✅ shallowRef:Follow Only .value Replace All
const products = shallowRef([
  { id: 1, name: 'Product 1' },
  // ... 10,000 item (Will not be Proxy-wrapped) ...
])

// ✅ markRaw:The tagged object never responds(Fastest)
const staticConfig = markRaw({
  apiUrl: 'https://api.example.com',
  version: '1.0'
})

// Replace All
products.value = await fetchProducts()  // Trigger only 1 Next Update

(3) الإيرادات

بعد التحسين:



3. اعتراض الوكيل: حجر الزاوية في تفاعلية Vue 3

(1) ما المقصود بالوكيل؟

الوكيل (Proxy) هو غلاف كائن تم تقديمه في ES6 — ويمكنه اعتراض 13 نوعًا من عمليات الكائنات (get، set، has، deleteProperty، إلخ).

JS
// Basics Proxy Example
const target = { name: 'Alice', age: 25 }

const proxy = new Proxy(target, {
  get(target, key) {
    console.log(`Read ${key}`)
    return Reflect.get(target, key)
  },
  set(target, key, value) {
    console.log(`Write ${key} = ${value}`)
    return Reflect.set(target, key, value)
  }
})

proxy.name  // Output "Read name",Back 'Alice'
proxy.age = 26  // Output "Write age = 26"

(2) التنفيذ التفاعلي في Vue 3 (نسخة مبسطة)

JS
// Vue 3 Inside reactivity A Simplified Implementation of a Package
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      // 1. Dependency Collection:Track who is using this property
      track(target, key)
      
      // 2. Recursive Wrapping:Nested objects also become reactive
      const value = Reflect.get(target, key, receiver)
      if (isObject(value)) {
        return reactive(value)
      }
      return value
    },
    set(target, key, value, receiver) {
      // 1. Setting Value
      const oldValue = target[key]
      const result = Reflect.set(target, key, value, receiver)
      
      // 2. Trigger an update:Notify all locations where this property is used
      if (oldValue !== value) {
        trigger(target, key)
      }
      return result
    }
  })
}

(3) Proxy مقابل Object.defineProperty

الجانب Object.defineProperty (Vue 2) Proxy (Vue 3)
دعم المصفوفات يتطلب معالجة خاصة دعم أصلي
خريطة/مجموعة
إضافة خاصية جديدة يتطلب استخدام Vue.set Auto-react
الأداء المتوسط أفضل (الوكيل البطيء)
دعم المتصفحات IE9 وما بعده غير مدعوم في IE (يتطلب استخدام «بوليفيل»)


4. التتبع + التشغيل: تجميع التبعيات وتشغيل التحديثات

(1) مخطط التدفق الكامل

100%
sequenceDiagram
    participant T as Template
    participant C as Component
    participant R as Reactive<br/>(Proxy)
    participant W as WeakMap<br/>(target → Map)
    participant D as Data
    
    T->>C: 1. Render (Read count)
    C->>R: 2. get count
    R->>W: 3. track(target, 'count')
    W->>W: 4. Record the current activeEffect
    R->>D: 5. Read 0
    R-->>C: 6. Return 0
    C-->>T: 7. Display "Stock: 0"
    
    Note over D,T: The user clicks the button
    
    D->>R: 8. set count = 1
    R->>W: 9. trigger(target, 'count')
    W->>W: 10. Find all dependencies
    R->>C: 11. Scheduling Update
    C-->>T: 12. Display "Stock: 1"

(2) بنية تخزين التبعيات العالمية

JS
// Vue 3 Internal Data Structures
const targetMap = new WeakMap()
// targetMap = { obj1: Map1, obj2: Map2, ... }
// Map1 = { key1: Set1, key2: Set2, ... }
// Set1 = Set<activeEffect1, activeEffect2, ...>

function track(target, key) {
  // 1. Get target Corresponding Map
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  
  // 2. Get key Corresponding Set
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }
  
  // 3. Record the currently active effect
  if (activeEffect) {
    dep.add(activeEffect)
  }
}

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  
  const dep = depsMap.get(key)
  if (dep) {
    // Notify all dependencies to re-execute
    dep.forEach(effect => effect.run())
  }
}

(3) 5 نقاط رئيسية

النقاط الرئيسية الوصف
تتبع عند الحصول قراءة الخاصية → تسجيل "من يستخدمها"
عند تعيين مشغل تغيير الخاصية → إخطار "جميع المستخدمين"
تخزين WeakMap التخلص التلقائي من الذاكرة (يتم مسحها تلقائيًا عند تدمير الكائن المستهدف)
activeEffect التأثير الذي يتم تنفيذه حاليًا (render / watch / computed)
المعالجة المجمعة تؤدي التعديلات المتعددة إلى تحديث واحد فقط (nextTick)


5. التنفيذ الداخلي لـ ref

(1) شفرة المصدر لـ ref (النسخة المبسطة)

JS
// Vue 3 ref Internal Implementation
class RefImpl {
  constructor(value) {
    this._value = isObject(value) ? reactive(value) : value
  }
  
  get value() {
    // 1. track
    track(this, 'value')
    return this._value
  }
  
  set value(newValue) {
    // 1. Compare the old and new values
    if (hasChanged(this._value, newValue)) {
      this._value = isObject(newValue) ? reactive(newValue) : newValue
      // 2. trigger
      trigger(this, 'value')
    }
  }
}

function ref(value) {
  return new RefImpl(value)
}

(2) كيف تعمل عملية فك الضغط التلقائي

VUE
<template>
  <!-- Automatic unpacking in templates:{{ count }} rather than {{ count.value }} -->
  <p>{{ count }}</p>
</template>

<script setup>
import { ref } from 'vue'
const count = ref(0)
console.log(count.value)  // 0(JS Domestic Demand .value)
console.log(count)        // 0(devtools Displayed in the middle 0)
</script>

المبدأ: أثناء ترجمة القالب، يقوم المُترجم تلقائيًا بإضافة .value إلى ref (لا داعي لكتابته يدويًّا).



6. التنفيذ الداخلي لـ computed

(1) tag «كسول + قذر»

JS
// Vue 3 computed Simplified Implementation
function computed(getter) {
  const ref = {
    // 1. Cache value
    _value: undefined,
    
    // 2. Has it expired?(Needs to be recalculated)
    _dirty: true,
    
    // 3. getter
    get value() {
      // Lazy Evaluation:Counted only on the first visit
      if (this._dirty) {
        this._value = effect(getter)  // Collect Dependencies
        this._dirty = false
      }
      
      // 4. track(Who is using me?)
      track(this, 'value')
      return this._value
    }
  }
  
  // 5. When dependencies change,Mark dirty
  effect(() => getter()).scheduled = () => {
    ref._dirty = true
    trigger(ref, 'value')  // For triggering computed Component Updates
  }
  
  return ref
}

(2) آلية التخزين المؤقت

JS
const count = ref(0)
const double = computed(() => count.value * 2)

console.log(double.value)  // 0(First Calculation)
console.log(double.value)  // 0 (Cached, Do not recalculate)
count.value = 5
console.log(double.value)  // 10(Recalculation)


7. تحسين الأداء: shallowRef / shallowReactive / markRaw

(1) shallowRef: استجابة سطحية

JS
import { shallowRef, triggerRef } from 'vue'

const products = shallowRef([
  { id: 1, name: 'iPhone', price: 999 },
  { id: 2, name: 'MacBook', price: 2499 }
])

// ❌ Not responding(Edit .value Internal Properties)
products.value[0].name = 'iPad'
// Will not trigger an update(shallowRef Follow Only .value Replace All)

// ✅ Trigger an update(Replace All)
products.value = [...products.value, { id: 3, name: 'iPad' }]

// ✅ Force Trigger(Manually after making internal changes trigger)
products.value[0].name = 'iPad'
triggerRef(products)

(2) shallowReactive: تفاعلية سطحية

JS
import { shallowReactive } from 'vue'

const state = shallowReactive({
  user: { name: 'Alice' },
  settings: { theme: 'dark' }
})

// ✅ Response:Changes to Top-Level Properties
state.user = { name: 'Bob' }

// ❌ Not responding:Nested Properties
state.user.name = 'Charlie'

(3) markRaw: لا يستجيب أبدًا

JS
import { markRaw, reactive } from 'vue'

// Examples of Third-Party Libraries (e.g. ECharts, Mapbox) No need for reactive design
const map = markRaw(new Map())
const chart = markRaw(echarts.init(element))

const state = reactive({
  map,    // ✅ Will not be Proxy Packaging
  chart   // ✅ Performance Improvements 30%
})

(4) 5 حالات استخدام رئيسية

السيناريو الحل تحسين الأداء
قائمة البيانات الضخمة (100,000+) shallowRef 50x
أمثلة على المكتبات الخارجية markRaw 30%
إنشاء/إزالة الكائنات بشكل متكرر shallowReactive 20%
التكوين للقراءة فقط markRaw 10%
البيانات الثابتة shallowRef 5%


8. مثال كامل: تطبيق مبادئ التصميم المتجاوب

▶ مثال: 1. نسخة مبسطة من «المسار + المشغل»

JS 📖 للعرض فقط
// Simulation Vue 3 Responsive Systems
const targetMap = new WeakMap()
let activeEffect = null

function track(target, key) {
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }
  if (activeEffect) {
    dep.add(activeEffect)
  }
}

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const dep = depsMap.get(key)
  if (dep) dep.forEach(effect => effect())
}

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)
      return Reflect.get(target, key)
    },
    set(target, key, value) {
      const result = Reflect.set(target, key, value)
      trigger(target, key)
      return result
    }
  })
}

// Usage
const state = reactive({ count: 0 })
let render = null

// Simulation render Function
activeEffect = () => console.log(`Render: count = ${state.count}`)
render = activeEffect
render()  // Output "Render: count = 0"

state.count = 1  // Trigger render,Output "Render: count = 1"
42 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

▶ مثال: 2. مقارنة بين مؤشرات الأداء الخمسة الرئيسية

الوضع الأداء عند 100,000 سطر الذاكرة قابل للتطبيق
ref([]) 100 مللي ثانية 100 ميغابايت بيانات صغيرة
reactive({...}) 100 مللي ثانية 100 ميغابايت كائنات متداخلة
shallowRef([]) 5 مللي ثانية 5 ميغابايت قائمة كبيرة
shallowReactive({...}) 30 مللي ثانية 30 ميغابايت كائن سطحي
markRaw({...}) 2 مللي ثانية 2 ميغابايت مثيل تابع لجهة خارجية

▶ مثال: 3. البنية الداخلية لـ ref + reactive

JS
import { ref, reactive, shallowRef, markRaw } from 'vue'

// ref:Wrap any value
const count = ref(0)
console.log(count)  // RefImpl { _value: 0, __v_isRef: true, ... }

// reactive:Items to Be Packaged
const state = reactive({ count: 0 })
console.log(state)  // Proxy { count: 0, ... }

// shallowRef: Shallow
const list = shallowRef([1, 2, 3])
console.log(list)  // RefImpl { _value: [1, 2, 3] }  // Arrays are not reactive Packaging

// markRaw:Does not respond at all
const map = markRaw(new Map())
console.log(map)  // Map { ... }  // Regular Map,No Proxy
▶ جرّب الكود

▶ مثال: 4. خمسة أسباب جذرية شائعة للفشل

سيناريو الفشل السبب الجذري الحل
استبدال الكائن التفاعلي بأكمله لقد تغيرت الإشارة التفاعلية قم بتغليفها في متغير «ref» أو تعديل خصائصها
تحليل الاستجابة المفقودة تحليل القيمة الأصلية استخدام toRefs
تغيير مصفوفة shallowRef لا يُطلق التحديث shallowRef يتتبع فقط استبدال .value استبدل المصفوفة أو استخدم triggerRef
المهمة غير المتزامنة لا تستجيب الدالة غير المتزامنة خارج المسار التغليف باستخدام ref/reactive
لا يتم تحديث خصائص الكائنات المتداخلة تم إصلاح المشكلة في Vue 3 (يتطلب Vue 2 استخدام Vue.set) التحديثات التلقائية

▶ مثال: 5. معيار أداء الخادم الوكيل

JS
// Performance Testing:10,000 A property
const obj = {}
for (let i = 0; i < 10000; i++) {
  obj[`key${i}`] = i
}

console.time('Object.defineProperty')
// ... 10,000 getter/setter
console.timeEnd('Object.defineProperty')
// Usually 50-100ms

console.time('Proxy')
const proxy = new Proxy(obj, { /* ... */ })
console.timeEnd('Proxy')
// Usually 1-5ms(Create only when accessed)
▶ جرّب الكود

▶ مثال: 6. 5 نصائح أساسية للتصحيح

JS
// 1. watchEffect Printing Dependencies
watchEffect(() => {
  console.log('Dependency:', { a: state.a, b: state.b })
})

// 2. Trigger a manual update(forceUpdate)
import { triggerRef } from 'vue'
triggerRef(stateRef)

// 3. Check the responsive status
import { isReactive, isRef, isReadonly, isProxy } from 'vue'
isReactive(obj)  // true/false
isRef(refObj)    // true/false

// 4. Original Object(Remove Proxy)
import { toRaw } from 'vue'
const raw = toRaw(reactiveObj)

// 5. Performance Labels
import { markRaw } from 'vue'
const fast = markRaw(expensiveObj)
▶ جرّب الكود

❓ أسئلة شائعة

س لماذا يستخدم Vue 3 «Proxy» بدلاً من «Object.defineProperty»؟
ج يدعم «Proxy» المصفوفات و«Maps» و«Sets» بشكل أصلي، ويمكنه اعتراض المزيد من العمليات (13 مقابل 2)، ويستجيب تلقائيًا لإضافات الخصائص الجديدة، ويقدم أداءً أفضل (الوكيل المتأخر). العيب الوحيد هو أنه لا يدعم IE11.
س أين يتم استدعاء track وtrigger؟
ج يتم استدعاء track في الأسلوب get لكائن تفاعلي (لتسجيل التبعيات عند استدعاء get). يتم استدعاء trigger في الطريقة set (لإخطار جميع التبعيات عند استدعاء set).
س ما الفرق بين shallowRef وref؟
ج ref هو استجابة عميقة (الكائنات المتداخلة هي أيضًا وكلاء). أما shallowRef فيقتصر على تتبع استبدال .value بالكامل ولا يتعامل مع الخصائص الداخلية. وهو مناسب للقوائم الكبيرة (100,000+ صف).
س كيف يعرف computed متى تتغير إحدى التبعيات؟
ج يقوم computed داخليًّا بتغليف دالة الاسترجاع (getter) في effect. وعندما تتغير البيانات التفاعلية المستخدمة في دالة الاسترجاع، يتم تشغيل effect، مما يؤدي إلى إعادة تعيين tag dirty بحيث يتم إعادة حساب القيمة عند الوصول إليها في المرة التالية.
س ما الفرق في التنفيذ الداخلي بين watch وwatchEffect؟
ج watch تحدد المصدر (التبعية) بشكل صريح، وتستقبل دالة الاستدعاء كلًا من newVal وoldVal. أما watchEffect فلا يحدد مصدرًا؛ فهو يتتبع التبعيات تلقائيًا داخل جسم الدالة، ولا يتلقى دالة الاستدعاء إلا القيمة الجديدة.
س كيف يمكن تشغيل التحديث عند تعديل الخصائص الداخلية باستخدام shallowRef؟
ج قم بتشغيله يدويًّا باستخدام triggerRef(shallowRef). السيناريو: فرض تحديث العرض بعد إجراء تعديل جماعي على الخصائص الداخلية.
س ما الذي يعرضه الوكيل (Proxy) المُغلف بـ reactive في وحدة التحكم؟
ج يعرض Proxy { ... } wrapper object; the properties of the original object are not visible. Use toRaw(proxyObj) لاسترداد الكائن الأصلي.

📖 ملخص


📝 تمارين

  1. أسئلة أساسية (مستوى الصعوبة: ⭐)

    تنفيذ نسخة مبسطة من نظام تفاعلي (يعتمد على البروكسي):

    • دالة تفاعلية مكونة من 5 أسطر
    • 5 خطوط تتبع + مشغل
    • العرض التوضيحي: الضبط 100 مرة → تشغيل التأثير مرة واحدة (المعالجة المجمعة)
  2. مسائل متقدمة (مستوى الصعوبة: ⭐⭐)

    تحسين القوائم الكبيرة باستخدام shallowRef وmarkRaw:

    • قائمة تضم 1,000 منتج (باستخدام shallowRef)
    • يتضمن مثيلات لمكتبات تابعة لجهات خارجية (باستخدام markRaw)
    • قياس استخدام الذاكرة والتحسينات في معدل الإطارات في الثانية (FPS)
  3. مسألة التحدي (مستوى الصعوبة: ⭐⭐⭐)

    تنفيذ «أداة تتبع التبعيات» كاملة:

    1. تطبع الدالة watchEffect جميع التبعيات
    2. triggerRef: تشغيل التحديث يدويًّا
    3. شرح وظائف toRaw و isReactive و isRef المساعدة
    4. تحليل الأسباب الجذرية لخمسة سيناريوهات للفشل (استبدال المرجع / التفكيك / المصفوفات / عدم التزامن / التداخل)
    5. معايير الأداء: مقارنة الذاكرة بين ref و shallowRef و markRaw
Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%