الخاصيات المُراقبة
مثال أساسي
الخاصيات المحسوبة تسمح لنا بحساب القيم المشتقة بشكل تصريحي. ومع ذلك، هناك حالات حيث نحتاج إلى تنفيذ "تأثيرات جانبية" بالرد على تغييرات الحالة - على سبيل المثال، تعديل DOM، أو تغيير جزء آخر من الحالة بناءً على نتيجة عملية غير متزامنة.
مع الواجهة التركيبية، يمكننا استخدام الدالة watch
لتشغيل دالة عند تغيير حالة تفاعلية :
vue
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('عادة ما تحتوي الأسئلة على علامة استفهام. ;-)')
watch(question, async (newQuestion) => {
if (newQuestion.indexOf('?') > -1) {
answer.value = 'قيد التفكير ...'
try {
const res = await fetch('https://yesno.wtf/api')
answer.value = (await res.json()).answer
} catch (error) {
answer.value = 'خطأ! تعذر الوصول إلى الخادم. ' + error
}
}
})
</script>
<template>
<p>
اطرح سؤال نعم/لا:
<input v-model="question" />
</p>
<p>{{ answer==='yes'?'نعم':'لا' }}</p>
</template>
أنواع مصادر الدالة المُراقبة
الوسيط الأول للدالة watch
يمكن أن يكون أنواع مختلفة من "المصادر" التفاعلية : يمكن أن يكون ref (بما في ذلك الخاصيات المحسوبة)، كائن تفاعلي ، دالة مُحصِّلة، أو مصفوفة من مصادر متعددة :
js
const x = ref(0)
const y = ref(0)
// ref وحيد
watch(x, (newX) => {
console.log(`x يساوي ${newX}`)
})
// دالة مُحصِّلة
watch(
() => x.value + y.value,
(sum) => {
console.log(`مجموع x + y يساوي: ${sum}`)
}
)
// مصفوفة من مصادر متعددة
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x يساوي ${newX} و y يساوي ${newY}`)
})
تجدر الإشارة إلى أنه لا يمكنك مراقبة خاصية من كائن تفاعلي كهذا المثال :
js
const obj = reactive({ count: 0 })
// لن يشتغل هذا لأننا نمرر رقمًا إلى
// watch()
watch(obj.count, (count) => {
console.log(`العداد: ${count}`)
})
بدلاً من ذلك استعمل دالة مُحصِّلة لارجاع الخاصية:
js
// بدلاً من ذلك استعمل دالة مُحصِّلة لارجاع الخاصية:
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)
الخاصيات المُراقبة العميقة
عندما تستدعي ()watch
مباشرةً على كائن تفاعلي، سيُنشئ تلقائيًا دالة مُراقبة عميقة - ستُشغَّل دالة رد النداء على جميع التغييرات المتداخلة :
js
const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {
// تشغل عند التغييرات المتداخلة للخاصية ملاحظة:
// `newValue` سيكون مساويًا لـ `oldValue` هنا
// لأنهما يشيران إلى نفس الكائن!
})
obj.count++
This should be differentiated with a getter that returns a reactive object - in the latter case, the callback will only fire if the getter returns a different object:
js
watch(
() => state.someObject,
() => {
// تشغل فقط عند استبدال state.someObject
}
)
ومع ذلك، يمكنك فرض الحالة الثانية داخل دالة مراقبة عميقة عن طريق استخدام الخيار "deep" بشكل صريح :
js
watch(
() => state.someObject,
(newValue, oldValue) => {
// ملاحظة: `newValue` سيكون مساويًا لـ `oldValue` هنا
// *إلا إذا* تم استبدال state.someObject
},
{ deep: true }
)
استخدمها بحذر
تتطلب المراقبة العميقة مراجعة جميع الخصائص المتداخلة في الكائن المُراقَب، ويمكن أن تكون مكلفة عند استخدامها على بنية بيانات كبيرة. استخدمها فقط عند الضرورة وتأكد من تأثيرات الأداء.
الدوال المُراقِبة الفورية
الدالة watch
خاملة افتراضيًا: لن تُستدعى دالة رد النداء حتى يُغيَّر المصدر المراقَب. ولكن في بعض الحالات قد نرغب في تشغيل نفس شيفرة رد النداء بشكل فوري - على سبيل المثال، قد نرغب في جلب بعض البيانات الأولية ثم إعادة جلبها عند تغيير الحالة المرتبطة بها.
يمكننا فرض تشغيل دالة رد النداء للخاصية المُراقبة فورًا عن طريق التصريح بها باستخدام كائن يحتوي على دالة المعالجة handler
لتنفيذ الشيفرة المستندة على تغير المصدر المُراقَب وخيار immediate: true
:
js
watch(source, (newValue, oldValue) => {
// سيتم تشغيله فورًا ثم مرة أخرى عند تغيير المصدر `source`
}, { immediate: true })
()watchEffect
من الشائع أن تستخدم دالة رد النداء نفس الحالة المرتبطة بالمصدر المُراقَب. على سبيل المثال، فإن الشيفرة التالية تستخدم مُراقِب لتحميل مورد عن بعد عند تعيين المتغير التفاعلي todoId
:
js
const todoId = ref(1)
const data = ref(null)
watch(
todoId,
async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
},
{ immediate: true }
)
على وجه الخصوص، تجدر الإشارة إلى أن الدالة المُراقِبة تستخدم todoId
مرتين، مرة كمصدر ومرة أخرى داخل الدالة المُعالجة.
يمكن تبسيط هذا باستخدام ()watchEffect
. يسمح لنا ()watchEffect
بتتبع الاعتماديات التفاعلية للدالة المعالجة تلقائيًا. يمكن إعادة كتابة المُراقِب أعلاه كما يلي:
js
watchEffect(async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
})
هنا، ستُشغَّل الدالة المُعالِجة فورًا، لا تحتاج إلى تحديد immediate: true
. خلال تنفيذها، سيتم تتبع todoId.value
تلقائيًا كاعتمادية (مماثلة للخاصيات المحسوبة). عندما يتغير todoId.value
، سيتم تشغيل الدالة المعالجة مرة أخرى. مع ()watchEffect
، لم نعد نحتاج إلى تمرير todoId
بشكل صريح كقيمة مصدرية.
يمكنك التحقق من هذا المثال لـ ()watchEffect
و كيفية تحميل البيانات التفاعلية داخل دالتها المعالجة.
بالنسبة للأمثلة مثل هذه، مع اعتمادية واحدة فقط، فإن فائدة ()watchEffect
غير ملموسة نسبيا. ولكن hلدوال المُراقِبة التي لديها عدة اعتماديات، فإن استخدام ()watchEffect
يزيل قائمة الاعتماديات المضافة يدويًا. بالإضافة إلى ذلك، إذا كنت بحاجة إلى مراقبة عدة خاصيات في سلسلة بيانات متداخلة، فقد تثبت الدالة ()watchEffect
فعاليتها مقارنة بالدالة المُراقِبة العميقة، حيث سيتم تتبع الخاصيات المستخدمة فقط في الدالة المعالجة، بدلاً من تتبعها جميعا بشكل تكراري .
ملاحظة
()watchEffect
يتتبع الاعتماديات فقط خلال تنفيذها المتزامن. عند استخدامه مع دالة مُعالِجة غير متزامنة، وحدها الخاصيات التي يتم الوصول إليها قبل النبضة الأولى لـawait
ستُتبَّع .
watch
مقابل watchEffect
watch
و watchEffect
كلتاهما تسمحان لنا بتنفيذ تأثير جانبية بشكل تفاعلي. الفرق الرئيسي بينهما هو طريقة تتبع اعتمادياتهما التفاعلية :
watch
تتبع فقط المصدر المراقب بشكل صريح. لن يتم تتبع أي شيء يتم الوصول إليه داخل الدالة المُعالجة. بالإضافة إلى ذلك، فإن الدالة المعالجة تتفاعل فقط عندما يتغير المصدر بالفعل.watch
تفصل التتبع الاعتمادي عن التأثير الجانبي، مما يمنحنا مزيدًا من التحكم الدقيق في متى يجب تشغيل الدالة المعالجة.watchEffect
, من ناحية أخرى، تجمع التتبع الاعتمادي والتأثير الجانبي في طور واحد. تتبع كل الخاصيات التفاعلية التي يتم الوصول إليها خلال تنفيذها بشكل متزامن.هذه الدالة تعتبر أكثر ملاءمة وعادة ما يؤدي ذلك إلى تقليل الشيفرة، ولكنه يجعل اعتمادياته التفاعلية أقل وضوحًا.
توقيت تنفيذ الدالة المعالجة
لما تُعدل حالة تفاعلية، فإنها قد تُشغل كل من تحديثات مكونات Vue والدوال المراقِبة المُنشأة من قبلك.
افتراضيا، تُشغل الدوال المراقِبة المُنشأة من قبل المستخدم قبل تحديثات مكونات Vue. هذا يعني أنه إذا حاولت الوصول إلى DOM داخل دالة مراقبة، فإن DOM سيكون عبارة عن حالة قبل أن تقوم Vue بتطبيق أي تحديثات.
إذا كنت ترغب في الوصول إلى DOM في دالة مراقبة بعد تحديث Vue، فيجب عليك تحديد خيار flush: 'post'
:
js
watch(source, callback, {
flush: 'post'
})
watchEffect(callback, {
flush: 'post'
})
تمتلك ()watchEffect
مسمى ملائم أيضًا لخاصية التنفيذ بعد التحديث، وهي الدالة ()watchPostEffect
:
js
import { watchPostEffect } from 'vue'
watchPostEffect(() => {
/* نفذت بعد تحديثات Vue */
})
إيقاف خاصية مراقِبة
الخاصيات المراقِبة المصرحة بشكل تزامني داخل setup()
أو <script setup>
مرتبطة بنسخة المكون الحامل لها، وسيتم إيقافها تلقائيًا عند فصله. في معظم الحالات، لا تهتم بشأن إيقاف الخاصية المراقِبة بنفسك.
الجوهر هنا هو أن الخاصية المراقِبة يجب أن تُنشأ بشكل متزامن : إذا تم إنشاء الخاصية المراقِبة في دالة رد نداء غير متزامنة، فلن تكون مرتبطة بالمكون الحامل لها ويجب إيقافها يدويًا لتجنب التسريبات في الذاكرة. هنا مثال:
vue
<script setup>
import { watchEffect } from 'vue'
// هذا سيوقف تلقائيًا
watchEffect(() => {})
//هذا لن يوقف تلقائيًا!
setTimeout(() => {
watchEffect(() => {})
}, 100)
</script>
من أجل إيقاف الخاصية المراقِبة يدويًا، استخدم الدالة المُرجَعة. هذا يعمل لكل من watch
و watchEffect
:
js
const unwatch = watchEffect(() => {})
// ... في وقت لاحق، عندما لا تعود محتاجا إلى المراقبة
unwatch()
تجدر الإشارة إلى أن هناك حالات نادرة جدًا حيث تحتاج إلى إنشاء مراقِبين بشكل غير متزامن، ويجب تفضيل الإنشاء المتزامن في أي وقت ممكن. إذا كنت بحاجة إلى الانتظار لبعض البيانات غير المتزامنة، يمكنك جعل شيفرة الدالة المراقبة شرطية بدلاً من ذلك :
js
// بيانات ستُحمّل بشكل غير متزامن
const data = ref(null)
watchEffect(() => {
if (data.value) {
// قم ببعض المعالجة عندما يتم تحميل البيانات
}
})