Tarkibga o'tish

02 β€” Reactivity (Reaktivlik)

⬅️ Oldingi: 01 β€” Vue asoslari Β· 🏠 README Β· Keyingi: 03 β€” Komponentlar ➑️


Bu Vue'ning yuragi. Buni chuqur tushunsang, qolgan hammasi oson. Yuzaki tushunsang β€” "nega yangilanmayapti?" degan buglar bilan kurashasan.

Reaktivlik nima?

State o'zgarsa, unga bog'liq hamma narsa (UI, computed, watcher) avtomatik yangilanadi.

Excel analogiyasi: C1 = A1 + B1. A1 ni o'zgartirsang, C1 o'zi qayta hisoblanadi. Vue ham shunday "bog'liqliklar grafini" (dependency graph) yuritadi.

Laravel analogiyasi: Eloquent'da $user->name β€” oddiy property. O'zgartirsang hech narsa bo'lmaydi. Vue'da reaktiv qiymat β€” model observer'iga o'xshaydi: o'zgarish "kuzatiladi" va kerakli reaksiya (re-render) ishga tushadi.


2.1 ref() β€” birlamchi (primitiv) qiymatlar uchun

import { ref } from 'vue'

const count = ref(0)
const name = ref('Oqil')
const user = ref({ id: 1, name: 'Ali' })   // obyekt ham bo'ladi

console.log(count.value)   // 0  β€” .value ORQALI o'qiladi
count.value++              // o'zgartirish ham .value bilan
count.value = 10

Nega .value? JavaScript'da primitivlar (number, string, boolean) qiymat bo'yicha uzatiladi β€” ularni "kuzatib" bo'lmaydi. Shu sabab Vue ularni { value: ... } obyektga o'raydi. Obyektni esa Proxy bilan kuzatish mumkin.

  • Template'da .value shart emas (Vue avtomatik ochadi/"unwrap" qiladi):
    <template>{{ count }}</template>  <!-- count.value emas -->
    
  • <script> ichida .value doim kerak.
// ❌ Eng ko'p uchraydigan xato
const total = ref(0)
total = total + 5          // ref obyektni son bilan almashtiryapsan β€” buziladi

// βœ…
total.value = total.value + 5

2.2 reactive() β€” faqat obyekt/massiv uchun

import { reactive } from 'vue'

const state = reactive({
  count: 0,
  user: { name: 'Ali' },
  todos: [],
})

state.count++              // .value YO'Q β€” to'g'ridan-to'g'ri
state.user.name = 'Vali'   // chuqur (deep) reaktiv
state.todos.push('x')

reactive β€” obyektni ES6 Proxy ga o'raydi. Property o'qilishi/yozilishi "tutib olinadi" (intercept) va kuzatiladi.

ref vs reactive β€” qaysi birini ishlatish?

ref reactive
Tur Har qanday (primitiv ham, obyekt ham) Faqat obyekt/massiv/Map/Set
Kirish .value To'g'ridan-to'g'ri
Almashtirish (reassign) x.value = newObj βœ… state = newObj ❌ reaktivlik yo'qoladi
Destructure Reaktivlik yo'qoladi (toRefs kerak) Reaktivlik yo'qoladi

Amaliy tavsiya: Hamma joyda ref ishlat. Bitta qoidani (.value) yodda tutish, ikkita modelni aralashtirishdan oson. reactive ning cheklovlari ko'p:

// reactive muammosi 1: reassign buziladi
let state = reactive({ count: 0 })
state = reactive({ count: 5 })   // eski kuzatuvchilar ulanib qoladi β€” bug

// reactive muammosi 2: destructure reaktivlikni uzadi
const { count } = reactive({ count: 0 })  // count endi oddiy son, reaktiv emas

// ref bilan bunday muammo yo'q:
const count = ref(0)
count.value = 5   // har doim ishlaydi

Vue jamoasining ko'p a'zolari ham "default β€” ref" deydi. reactive ni faqat aniq sabab bo'lsa ishlat.

Quyidagi diagramma ikkalasini va qaysi birini qachon ishlatishni solishtiradi:

ref va reactive: qaysi birini qachon ishlatish


2.3 computed() β€” hosilaviy (derived) qiymat

State'dan kelib chiqadigan qiymatni hisoblaydi va keshlaydi.

import { ref, computed } from 'vue'

const firstName = ref('Oqil')
const lastName = ref('Dev')

const fullName = computed(() => `${firstName.value} ${lastName.value}`)

console.log(fullName.value)   // "Oqil Dev"
firstName.value = 'Ali'
console.log(fullName.value)   // "Ali Dev" β€” avtomatik yangilandi

Laravel analogiyasi: computed = Eloquent accessor (getFullNameAttribute). Bog'liq atributlardan hosilaviy qiymat. Farqi β€” Vue'da u keshlanadi.

Computed vs Method β€” MUHIM

<script setup>
import { ref, computed } from 'vue'
const list = ref([1, 2, 3, 4, 5])

// computed β€” keshlanadi
const evenComputed = computed(() => list.value.filter(n => n % 2 === 0))

// method β€” har render'da QAYTA ishlaydi
function evenMethod() { return list.value.filter(n => n % 2 === 0) }
</script>

<template>
  {{ evenComputed }}   <!-- bog'liqlik (list) o'zgarmaguncha qayta hisoblanMAYDI -->
  {{ evenMethod() }}   <!-- har re-render'da qayta ishlaydi -->
</template>

computed bog'liqliklarini (list) kuzatadi. Ular o'zgarmasa β€” keshdagi qiymatni qaytaradi, qayta hisoblamaydi. Og'ir hisob-kitoblar uchun bu katta tejamkorlik.

Quyidagi diagramma keshlash mantig'ini ko'rsatadi β€” qachon qayta hisoblanadi, qachon keshdan oladi:

computed keshlash: bog'liqlik o'zgarmasa qayta hisoblamaydi

Writable computed (get/set)

const fullName = computed({
  get: () => `${firstName.value} ${lastName.value}`,
  set: (val) => {
    [firstName.value, lastName.value] = val.split(' ')
  }
})
fullName.value = 'Yangi Ism'   // set ishga tushadi

Computed qoidalari

  • Sof (pure) bo'lsin: faqat hisoblab qaytarsin, side-effect (API chaqirish, state o'zgartirish) qilmasin.
  • Argument qabul qilmaydi. Parametr kerak bo'lsa β€” computed funksiya qaytarsin yoki method ishlat.

2.4 watch() β€” o'zgarishga reaksiya (side-effect)

computed qiymat qaytaradi. watch esa o'zgarishga harakat qiladi (API, localStorage, log...).

import { ref, watch } from 'vue'

const query = ref('')

watch(query, (newVal, oldVal) => {
  console.log(`${oldVal} β†’ ${newVal}`)
  // masalan: API qidiruv
})

// Bir nechta manbani kuzatish
watch([firstName, lastName], ([newF, newL], [oldF, oldL]) => { ... })

// reactive obyekt property'sini kuzatish β€” getter funksiya bilan
watch(() => state.count, (newCount) => { ... })

watch opsiyalari

watch(source, callback, {
  immediate: true,   // komponent yuklanganda darrov bir marta ishga tushsin
  deep: true,        // obyekt ichidagi har qanday o'zgarishni kuzat (chuqur)
  once: true,        // faqat bir marta (Vue 3.4+)
})

deep haqida: ref ichidagi obyektning ichki property'si o'zgarsa, oddiy watch ko'rmasligi mumkin. Chuqur kuzatish kerak bo'lsa deep: true. Lekin u qimmat β€” katta obyektlarda ehtiyot bo'l. Ko'pincha aniq property'ni getter bilan kuzatish (() => state.user.name) yaxshiroq.


2.5 watchEffect() β€” bog'liqliklarni avtomatik aniqlaydi

watch da manbani aniq ko'rsatasan. watchEffect da esa β€” funksiya ichida ishlatilgan hamma reaktiv qiymat avtomatik bog'liqlikka aylanadi.

import { ref, watchEffect } from 'vue'

const userId = ref(1)

watchEffect(() => {
  // userId ishlatilgani uchun u avtomatik kuzatiladi
  console.log(`User ${userId.value} ni yuklash...`)
  fetchUser(userId.value)
})
// Darrov bir marta ishlaydi (immediate kabi), keyin userId har o'zgarganda qayta

watch vs watchEffect

watch watchEffect
Bog'liqlik Aniq ko'rsatiladi Avtomatik aniqlanadi
Eski qiymat Bor (oldVal) Yo'q
Boshlang'ich ishga tushish Default yo'q (immediate bilan) Doim darrov ishlaydi
Qachon Aniq manbaga reaksiya, eski qiymat kerak Bir nechta manbaga bog'liq side-effect

Quyidagi diagramma ikkalasining farqini yonma-yon ko'rsatadi:

watch va watchEffect farqi

Cleanup (tozalash)

watch(id, async (newId, oldId, onCleanup) => {
  const controller = new AbortController()
  onCleanup(() => controller.abort())   // oldingi so'rovni bekor qil (race condition)
  await fetch(`/api/x/${newId}`, { signal: controller.signal })
})

2.6 Reaktivlik ICHKI mexanizmi (nega ishlaydi)

Buni bilish β€” debugging'ni osonlashtiradi:

  1. Track (kuzatish): Komponent render bo'lganda yoki computed/watchEffect ishlaganda, ular o'qigan har bir reaktiv property "bu effekt menga bog'liq" deb belgilanadi (Proxy get trap orqali).
  2. Trigger (qo'zg'atish): Property o'zgarganda (Proxy set trap), Vue o'sha propertyga bog'liq hamma effektni qayta ishga tushiradi.
[reactive obyekt] --get--> kim o'qiyapti? --> bog'liqlikni qayd qil
                  --set--> o'zgardi! --> bog'liq effektlarni qayta ishga tushir

Quyidagi diagramma bu ichki mexanizmni (track va trigger) ko'rsatadi:

Reaktivlik ichki mexanizmi: Proxy get track va set trigger

Bundan kelib chiqadigan muhim cheklovlar:

// ❌ Yangi property qo'shish (reactive obyektda eski Vue'larda muammo edi, Vue 3 Proxy bunda yaxshi, lekin ref afzal)
// ❌ Destructure reaktivlikni uzadi:
const { count } = reactive({ count: 0 })   // count β€” oddiy son
// βœ… toRefs bilan saqlash:
import { toRefs } from 'vue'
const state = reactive({ count: 0, name: 'x' })
const { count, name } = toRefs(state)   // count.value, name.value β€” reaktiv qoladi

2.7 Foydali reactivity yordamchilari

import { toRef, toRefs, unref, isRef, toRaw } from 'vue'

isRef(x)        // x ref'mi?
unref(x)        // ref bo'lsa .value, bo'lmasa o'zini qaytaradi (x?.value ?? x)
toRaw(proxy)    // Proxy'dan asl obyektni oladi (kuzatuvsiz)
toRef(obj,'k')  // obyekt property'sidan ref yasaydi (bog'lanish saqlanadi)
toRefs(obj)     // butun obyektni ref'lar obyektiga aylantiradi

Advanced (kerak bo'lganda): - shallowRef β€” faqat .value almashishini kuzatadi, ichini emas (katta obyektlar uchun performance). - readonly β€” o'zgartirib bo'lmaydigan reaktiv nusxa. - markRaw β€” obyektni reaktiv qilmaslikni belgilaydi (masalan, 3rd-party class instansiyasi).


Xulosa

  • ref β€” default tanlov, hammasi uchun, .value (script'da)
  • reactive β€” faqat obyekt, cheklovlari ko'p, ehtiyotkorlik bilan
  • computed β€” hosilaviy + keshlangan qiymat (Eloquent accessor kabi)
  • watch β€” aniq manbaga reaksiya, eski qiymat bor
  • watchEffect β€” avtomatik bog'liqlik, doim darrov
  • Reaktivlik = Proxy get (track) + set (trigger)
  • Destructure reaktivlikni uzadi β†’ toRefs

Oltin qoida: Qiymat kerakmi β†’ computed. Harakat kerakmi β†’ watch.


🎯 Masalalar (kamida 22 ta)

ref / reactive (1–6)

  1. price (ref) va qty (ref) bo'lsin. total ni computed qilib chiqar (price*qty).
  2. reactive({ x:0, y:0 }) ob'yektini yarat, ikki tugma x va y ni oshirsin, ekranda ko'rsat.
  3. Yuqoridagi reactive obyektni const { x } = state qilib destructure qil β€” nega yangilanmayapti? Tushuntir va toRefs bilan tuzat.
  4. ref ichida obyekt: const user = ref({ name:'', age:0 }). Inputlardan name/age yangilansin (.value.name).
  5. ❓ count = ref(0); count = count + 1 nega xato? Kodga izoh yozib to'g'rila.
  6. reactive massiv: state.items ga element qo'shish/o'chirish tugmalari (push, filter).

computed (7–13)

  1. Full name: firstName, lastName β†’ fullName computed.
  2. Writable computed (β˜…): fullName get/set bilan; bitta inputga to'liq ism yozilsa, firstName/lastName ajralib saqlansin.
  3. Filtr: Mahsulotlar massivi + minPrice (ref). filtered computed faqat price >= minPrice larni qaytarsin.
  4. Saralash (β˜…): Massivni asc/desc toggle qiluvchi tugma; sorted computed mos saralasin (asl massivni mutatsiya qilmasdan β€” [...arr].sort()).
  5. Statistika: Sonlar massividan sum, avg, max, min ni 4 ta computed qilib chiqar.
  6. Computed vs method (β˜…): Bir xil og'ir hisob-kitobni computed va method qilib yoz, console.log qo'yib, qaysi biri kamroq chaqirilishini kuzat. Xulosani yoz.
  7. Savat (β˜…): [{name,price,qty}] savat. subtotal (har element), grandTotal, itemCount computed bo'lsin.

watch / watchEffect (14–20)

  1. Logger: name (ref) o'zgarsa, eski→yangi qiymatni console.log qil.
  2. localStorage sync (β˜…): theme ref'ini watch qilib localStorage ga saqla; sahifa ochilganda undan o'qib boshlang'ich qiymat qil.
  3. immediate: watch ga immediate:true ber, komponent yuklanganda darrov ishlashini ko'rsat.
  4. deep (β˜…): ref({user:{name}}). Oddiy watch ichki o'zgarishni ko'rmasligini, deep:true ko'rishini namoyish qil.
  5. watchEffect: a, b ref'lariga bog'liq watchEffect; ikkalasidan biri o'zgarsa qayta ishlasin. Keyin shuni watch([a,b]) ga aylantir β€” farqni yoz.
  6. Debounced search (β˜…β˜…): Qidiruv inputi. watch ichida setTimeout + onCleanup bilan oldingi timer'ni tozalab, 400ms kechikish bilan "qidiruv yuborildi" deb log qil.
  7. Race condition (β˜…β˜…): userId (ref) o'zgarsa "API" (setTimeout bilan soxta) chaqir; onCleanup orqali eski so'rovni bekor qilib, faqat oxirgisi natija berishini ta'minla.

Aralash loyiha (21–25)

  1. Konvertor (β˜…): Som ↔ Dollar. Bir inputga som yozsang dollar chiqsin va aksincha (2 ta writable computed yoki watch).
  2. Parol kuchi (β˜…): Parol inputi. computed bilan kuch darajasini hisobla (uzunlik, raqam, katta harf, belgi) va rangli indikator ko'rsat.
  3. To-Do v1 (β˜…β˜…): 01-moduldagi To-Do'ni yaxshila: activeCount/completedCount computed, filter (all/active/done) computed, ro'yxatni localStorage ga watch(deep) bilan saqla.
  4. Forma validatsiya (β˜…β˜…): Email + parol. Har biri uchun computed xato xabari (isEmailValid, isPasswordValid); canSubmit computed ikkalasi to'g'ri bo'lsagina true.
  5. Real-time filter+sort+search (β˜…β˜…β˜…): Mahsulotlar jadvali: qidiruv (matn), kategoriya filtri, narx bo'yicha saralash β€” uchalasi bitta displayProducts computed orqali zanjir bilan birlashsin.

βœ… Tanlangan yechimlar

8 β€” Writable computed
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('Oqil')
const lastName = ref('Dev')

const fullName = computed({
  get: () => `${firstName.value} ${lastName.value}`,
  set: (val) => {
    const parts = val.trim().split(/\s+/)
    firstName.value = parts[0] ?? ''
    lastName.value = parts.slice(1).join(' ')
  },
})
</script>

<template>
  <input :value="fullName" @input="fullName = $event.target.value">
  <p>Ism: {{ firstName }} | Familiya: {{ lastName }}</p>
</template>
19 β€” Debounced search
<script setup>
import { ref, watch } from 'vue'
const query = ref('')

watch(query, (val, _old, onCleanup) => {
  const timer = setTimeout(() => {
    console.log('Qidiruv yuborildi:', val)   // bu yerda API
  }, 400)
  onCleanup(() => clearTimeout(timer))        // tez yozilsa oldingisini bekor qil
})
</script>

<template>
  <input :value="query" @input="query = $event.target.value" placeholder="Qidiruv...">
</template>
25 β€” Filter + sort + search zanjiri
<script setup>
import { ref, computed } from 'vue'
const products = ref([
  { id:1, name:'Laptop', cat:'tech', price:1200 },
  { id:2, name:'Olma', cat:'food', price:5 },
  { id:3, name:'Telefon', cat:'tech', price:800 },
])
const search = ref('')
const category = ref('all')
const sortDir = ref('asc')

const displayProducts = computed(() => {
  let list = products.value
  // 1) search
  if (search.value) {
    const q = search.value.toLowerCase()
    list = list.filter(p => p.name.toLowerCase().includes(q))
  }
  // 2) category
  if (category.value !== 'all') {
    list = list.filter(p => p.cat === category.value)
  }
  // 3) sort (nusxa olib β€” asl massivni buzmaymiz)
  return [...list].sort((a, b) =>
    sortDir.value === 'asc' ? a.price - b.price : b.price - a.price
  )
})
</script>

<template>
  <input :value="search" @input="search = $event.target.value" placeholder="Qidiruv...">
  <button @click="sortDir = sortDir === 'asc' ? 'desc' : 'asc'">
    Narx: {{ sortDir }}
  </button>
  <ul>
    <li v-for="p in displayProducts" :key="p.id">{{ p.name }} β€” ${{ p.price }}</li>
  </ul>
</template>

➑️ Keyingi: 03 β€” Komponentlar