مبانی Reactivity
API Preference
این صفحه و بسیاری از صفحات دیگر در این راهنما حاوی محتوای متفاوتی برای Options API و Composition API است. ترجیح فعلی شما Composition API است. شما میتوانید سبکهای API را با استفاده از سوئیچ "سبک مرجع API" در بالای نوار کناری سمت چپ تغییر دهید.
در Vue قابلیت Reactivity این امکان را فراهم میکند تا نسبت به تغییر دیتای یک متغیر آگاهی داشته باشیم. بطور کلی هنگامی که یک reactive توسط بخشی از برنامه خوانده میشود یا نوشته میشود سیستم Reactivity فریمورک از آن اطلاع مییابد و میتواند کارهای مختلفی را انجام دهد از جمله بروزرسانی DOM. توجه داشته باشید که وقتی از reactive صحبت میشود در اصل هدف reactive state است که در برخی از قسمتها برای راحتی به آن reactive گفته میشود. در Vue چند روش برای تعریف reactive وجود دارد که در ادامه به آن میپردازیم. (مترجم)
تعریف Reactive State
ref()
در Composition API، روش توصیه شده برای تعریف یک reactive استفاده از تابع ref()
است:
js
import { ref } from 'vue'
const count = ref(0)
ref()
آرگومان گرفته شده را درون یک آبجکت Ref قرار میدهد و با استفاده پراپرتی .value
برمیگرداند:
js
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
همچنین ببینید: Typing Refs
برای دسترسی به Refها درون تمپلیت یک کامپوننت، آنها را در تابع setup()
کامپوننت تعریف کنید و برگردانید:
js
import { ref } from 'vue'
export default {
// است Composition API یک هوک ویژه برای `setup`
setup() {
const count = ref(0)
// را در دسترس تمپلیت قرار میدهد ref
return {
count
}
}
}
template
<div>{{ count }}</div>
توجه کنید که نیاز نبود .value
را برای استفاده از Ref در تمپلیت بنویسیم. برای راحتی، Refها در تمپلیت به طور خودکار تنظیم میشوند (با چند استثنا).
شما همچنین میتوانید یک Ref را مستقیماً در event handlerها تغییر دهید:
template
<button @click="count++">
{{ count }}
</button>
برای زمانی که منطق پیچیدهتر داریم، میتوانیم توابعی برای تغییر Refها در همان scope تعریف کنیم و آنها را در کنار state به عنوان متد در دسترس قرار دهیم:
js
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
// نیاز است JavaScript در .value
count.value++
}
// تابع را هم در دسترس قرار بدهید
return {
count,
increment
}
}
}
متدهای در دسترس قرار داده شده میتوانند به عنوان event handlerها استفاده شوند:
template
<button @click="increment">
{{ count }}
</button>
مثال در Codepen بدون استفاده از ابزار بیلد.
<script setup>
ارائه دستی state (متغیرهای reactive) و متدها از طریق setup()
میتواند طولانی باشد. خوشبختانه، این کار در استفاده از Single-File Components (SFCs) قابل اجتناب است. ما میتوانیم با استفاده از <script setup>
نحوه استفاده را ساده کنیم:
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }}
</button>
</template>
importها، متغیرها و توابع تعریف شده در <script setup>
به طور خودکار در تمپلیت همان کامپوننت قابل استفاده هستند. به خود تمپلیت مانند یک تابع جاوااسکریپتی تعریف شده در همان scope فکر کنید - به طور طبیعی به هر چیزی که در کنار آن تعریف شده دسترسی دارد.
نکته
برای بقیه راهنما، ما عمدتاً از سینتکس SFC + <script setup>
برای نمونه کدهای Composition API استفاده خواهیم کرد، زیرا این متداولترین روش استفاده برای توسعهدهندگان Vue است.
اگر از SFC استفاده نمیکنید، همچنان میتوانید از Composition API با آپشن setup()
استفاده کنید.
چرا Ref ؟
شاید سؤال کنید چرا نیاز به Refهایی با .value
به جای متغیرهای ساده داریم. برای توضیح این موضوع، نیاز است مختصراً در مورد نحوه کار سیستم Reactivity در Vue بحث کنیم.
وقتی شما یک Ref را در تمپلیت استفاده میکنید، و مقدار آن Ref بعداً تغییر میکند، Vue به طور خودکار تغییر را تشخیص داده و DOM را به روزرسانی میکند. این کار از طریق سیستم Reactivity مبتنی بر ردیابی وابستگیها امکانپذیر است. وقتی یک کامپوننت برای اولین بار Render میشود، Vue هر Refی را که در طول Render استفاده شده ردیابی میکند. بعداً وقتی یک Ref تغییر میکند، کامپوننتهایی که آن را ردیابی میکنند را مجدداً Render خواهد کرد.
در جاوااسکریپت استاندارد، هیچ راهی برای تشخیص دسترسی یا تغییر متغیرهای ساده وجود ندارد. با این حال، ما میتوانیم عملیات Get و Set یک آبجکت را با متدهای Getter و Setter رهگیری کنیم.
پراپرتی .value
به Vue فرصت میدهد تا زمانی را که یک Ref دسترسی یا تغییر یافته را تشخیص دهد. در پشت پرده، Vue ردیابی را در Getter خود انجام میدهد و تغییرات را در Setter انجام میدهد. برای درک مفهوم آن میتوانید فکر کنید که یک Ref آبجکتی شبیه به این است:
js
// شبهه کد، پیادهسازی واقعی نیست
const myRef = {
_value: 0,
get value() {
track()
return this._value
},
set value(newValue) {
this._value = newValue
trigger()
}
}
ویژگی دیگر Refها این است که بر خلاف متغیرهای ساده، میتوان آنها را به توابع پاس داد در حالی که دسترسی به آخرین مقدار و ارتباط Reactivity حفظ میشود. این به ویژه هنگام Refactor کردن منطق پیچیده به کد قابل استفاده مجدد بسیار مفید است.
در بخش Reactivity in Depth در مورد سیستم Reactivity به طور مفصلتر بحث شده است.
Reactivity عمیق
Refها میتوانند داده با هر تایپی را نگه دارند، از جمله آبجکتها یا آرایههای تو در تو، یا ساختمانداده درونی جاوااسکریپت مثل Map
و مانند آن.
یک Ref داده خود را به طور عمیق reactive میکند. این بدان معناست که میتوانید انتظار داشته باشید تغییرات حتی زمانی که شما آبجکتها یا آرایههای تو در تو را تغییر میدهید نیز تشخیص داده شوند:
js
import { ref } from 'vue'
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// اینها به درستی کار خواهند کرد
obj.value.nested.count++
obj.value.arr.push('baz')
}
مقادیر غیر اولیه (Non-primitive) از طریق reactive()
به proxyهای reactive تبدیل میشوند که در زیر مورد بحث قرار گرفته است.
همچنین امکان خروج از reactivity عمیق با استفاده از shallow refs وجود دارد. برای shallow refs، تنها دسترسی به .value
ردیابی میشود. shallow refs میتواند برای عملکرد بهتر از مشاهده آبجکتهای بزرگ اجتناب کند، یا در مواردی که state داخلی توسط یک کتابخانه خارجی مدیریت میشود، استفاده شوند.
مطالعه بیشتر:
زمان بهروز رسانی DOM
وقتی state یک reactive را تغییر میدهید، DOM به طور خودکار بهروزرسانی میشود. با این حال، باید توجه داشت که بهروزرسانیهای DOM به صورت همزمان (synchronous) اعمال نمیشوند. به جای آن، Vue آنها را تا "next tick" در چرخه بهروزرسانی بافر میکند تا اطمینان حاصل شود که هر کامپوننت صرف نظر از اینکه چند تغییر state انجام داده، تنها یک بار بهروزرسانی شود.
برای انتظار به پایان رسیدن بهروزرسانی DOM پس از تغییر state، میتوانید از API سراسری nextTick() استفاده کنید:
js
import { nextTick } from 'vue'
async function increment() {
count.value++
await nextTick()
// بهروزرسانی شده است DOM اکنون
}
reactive()
روش دیگری برای تعریف reactive وجود دارد و آن استفاده از reactive()
است. بر خلاف ref که داده را در یک آبجکت خاص میپیچد، reactive()
خودِ آبجکت را reactive میکند:
js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
همچنین ببینید: Typing Reactive
استفاده در تمپلیت:
template
<button @click="state.count++">
{{ state.count }}
</button>
آبجکتهایی که reactive()
برمیگرداند در واقع JavaScript Proxies هستند و دقیقاً مثل آبجکتهایی عادی رفتار میکنند. تفاوت در این است که Vue قادر است دسترسی و تغییر همه پراپرتیهای یک آبجکت reactive را برای ردیابی و فعال کردن reactivity برسی کند.
reactive()
آبجکت را به طور عمیق تبدیل و ردیابی میکند: آبجکتهای تو در تو نیز هنگام دسترسی توسط reactive()
پیچیده میشوند. همچنین توسط ref()
زمانی که مقدار ref یک آبجکت است صدا زده میشود. مشابه shallow refs در اینجا shallowReactive()
برای خارج شدن از reactivity عمیق وجود دارد.
Reactive Proxy در برابر Original
مهم است که توجه داشته باشید مقدار برگشتی از reactive()
یک Proxy از آبجکت اصلی است که برابر با خودِ آبجکت اصلی نیست:
js
const raw = {}
const proxy = reactive(raw)
// برابر با اصلی نیست proxy
console.log(proxy === raw) // false
تنها خودِ proxy قابلیت reactivity را دارد - تغییر دادن آبجکت اصلی باعث اعمال بهروزرسانی نمیشود. بنابراین، بهترین روش کار با سیستم reactivity در Vue استفاده مستقیم از نسخههای proxy شده state است.
برای تضمین دسترسی یکسان به proxy، صدا زدن reactive()
روی همان آبجکت همیشه همان proxy را برمیگرداند و صدا زدن reactive()
روی یک proxy موجود نیز همان proxy را برمیگرداند:
js
// را برمیگرداند proxy روی همان آبجکت همان reactive() صدا زدن
console.log(reactive(raw) === proxy) // true
// خودِ آن را برمیگرداند proxy روی یک reactive() صدا زدن
console.log(reactive(proxy) === proxy) // true
این قانون برای آبجکتهای تو در تو نیز صدق میکند. به دلیل reactivity عمیق، آبجکتهای تو در تو درون یک آبجکت reactive نیز proxy هستند:
js
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
محدودیتهای reactive()
reactive()
چند محدودیت دارد:
محدودیت در تایپ داده: تنها بر روی تایپ object (آبجکتها، آرایهها و collection types مانند
Map
وSet
) کار میکند. نمیتواند تایپهای اولیه مانندstring
،number
یاboolean
را نگه دارد.امکان جایگزینی کامل آبجکت وجود ندارد: از آنجایی که ردیابی reactivity از طریق دسترسی به پراپرتیها انجام میشود، همیشه باید به همان رفرنس آبجکت reactive دسترسی داشته باشیم. این بدان معناست که نمیتوانیم به راحتی یک آبجکت reactive را "جایگزین" کنیم زیرا ارتباط reactivity با رفرنس اول از دست میرود:
jslet state = reactive({ count: 0 }) // دیگر ردیابی نمیشود ({ count: 0 }) رفرنس بالا // (از دست رفته است reactivity ارتباط) state = reactive({ count: 1 })
Not destructure-friendly: هنگامی که یک پراپرتی از جنس تایپهای اولیه جاوااسکریپت از یک آبجکت reactive را به متغیرهای محلی تبدیل میکنیم، یا آن پراپرتی را به عنوان ورودی تابعی ارسال میکنیم، ارتباط reactivity از دست میرود:
jsconst state = reactive({ count: 0 }) // جدا میشود state.count از count شده است destructure زمانی که let { count } = state // اصلی تحت تأثیر قرار نمیگیرد state count++ // تابع یک عدد ساده دریافت میکند و // نمیتواند تغییرات را ردیابی کند state.count // حفظ شود reactivity باید کل آبجکت را ارسال کنیم تا callSomeFunction(state.count)
به دلیل این محدودیتها، استفاده از ref()
را به عنوان API اولیه برای تعریف reactive state توصیه میکنیم.
جزئیات اضافی درباره تبدیل Ref
به عنوان پراپرتی آبجکت Reactive
یک Ref وقتی که بهعنوان پراپرتی از یک آبجکت reactive دسترسی یا تغییر داده میشود، بهصورت خودکار تبدیل میشود. به عبارت دیگر، مانند یک پراپرتی معمولی رفتار میکند:
js
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
اگر یک Ref جدید به یک پراپرتی که قبلاً لینک شده به یک Ref موجود است اختصاص داده شود، Ref قدیمی را جایگزین خواهد کرد:
js
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// جدا شده است state.count اصلی اکنون از Ref
console.log(count.value) // 1
تبدیل Ref تنها زمانی اتفاق میافتد که Ref درون یک آبجکت reactive عمیق قرار بگیرد. این قضیه برای پراپرتی یک آبجکت shallow reactive صدق نمیکند.
ملاحظات مهم در آرایهها و کالکشنها
بر خلاف آبجکتهای reactive، هنگامی که به یک Ref به عنوان عنصری از یک آرایه reactive یا تایپهایی مانند Map
دسترسی مییابیم، هیچ تبدیلی انجام نمیشود:
js
const books = reactive([ref('Vue 3 Guide')])
// نیاز دارد .value اینجا به
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// نیاز دارد .value اینجا به
console.log(map.get('count').value)
ملاحظات هنگام باز شدن Ref در تمپلیتها
تبدیل کردن Ref در تمپلیتها تنها در صورتی اعمال میشود که Ref یک پراپرتی مرتبه اول (top-level) در context رندر شده تمپلیت باشد.
در مثال زیر، count
و object
پراپرتیهای مرتبه اول هستند، اما object.id
مرتبه اول نیست:
js
const count = ref(0)
const object = { id: ref(1) }
بنابراین، این عبارت به درستی کار میکند:
template
{{ count + 1 }}
اما این یکی کار نمیکند:
template
{{ object.id + 1 }}
نتیجهٔ رندر [object Object]1
خواهد بود زیرا object.id
هنگام ارزیابی عبارت باز نمیشود و یک آبجکت Ref باقی میماند. برای رفع این مشکل، میتوانیم id
را به یک خاصیت مرتبه اول تبدیل کنیم:
js
const { id } = object
template
{{ id + 1 }}
حالا نتیجهٔ رندر 2
خواهد بود.
نکته دیگر این است که یک Ref در صورتی باز میشود که مقدار نهایی ارزیابی شده یک interpolation متن باشد (یعنی تگ {{ }}
)، بنابراین کد زیر 1 رندر میکند:
template
{{ object.id }}
این تنها یک ویژگی برای راحتی است و معادل {{ object.id.value }}
میباشد.