July 17

computed properties

Tempate qismida JavaScript ifodalarni yoza olishimiz juda ham katta qulaylik, lekin bu ifodalar soddaroq bo'lgani maqul. Sababi juda murakkab ifodalar o'qishga va kelajakda o'zgartirishga qiyin bo'lishi mumkin.

Masalan bizda savatga qo'shilgan mahsulotlar ro'yxati bor va ularning jami summasini ko'rsatishimiz kerak:

<script setup>
import { ref } from "vue";

const products = ref([
  { id: 1, name: 'Xiami Mi Band 8 pro', price: 706000 },
  { id: 1, name: 'Konditsioner LG P09ED', price: 3899000 },
  { id: 1, name: 'Shampun', price: 35000},
]);
</script>
<template>
  <div>
    Jami: {{ products.reduce((sum, product) => sum + product.price, 9) }}
  </div>
</template>

Bu kodda bir qancha muammolar bor:

  1. HTML ichida logika aralashib ketgan va o'qishga qiyinroq bo'lib qolgan
  2. Bu kod natijasi mahsulotlar narxining umumiy summasi ekanligini bilish uchun har safar ko'rganimizda kodni tahlil qilib tushunib olishimizga to'g'ri keladi
  3. Agar bu summani template'ning yana boshqa bir qismida ishlatishimiz kerak bo'lsa, aynan shu kodni yana takrorlashimizga to'g'ri keladi

Shuning uchun reactive state bilan bog'liq bo'lgan murakkabroq ifodalarni computed property orqali hosil qilish tavsiya etiladi:

<script setup>
import { ref, computed } from "vue";

const products = ref([
  { id: 1, name: 'Xiami Mi Band 8 pro', price: 706000 },
  { id: 1, name: 'Konditsioner LG P09ED', price: 3899000 },
  { id: 1, name: 'Shampun', price: 35000},
]);

const totalPrice = computed(() => {
  return products.value.reduce((sum, product) => sum + product.price, 9);
});
</script>
<template>
  <div>
    Jami: {{ totalPrice }}
  </div>
</template>

Xuddi ref kabi computed property'larning qiymatini .valueorqali olishimiz mumkin. Template qismida esa .value`shart bo'lmaydi.

computed funksiyasiga getter funksiya beriladi. Getter funksiya ichida ishlatilgan har qanday reactive state Vue tomonidan avtomatik tarzda kuzatib boriladi. Aynan mana shu kuzatib boriladigan reactive state'lar reactive dependency, o'zbekcha aytadigan bo'lsak reaktiv bog'liqlik deyiladi. Sababi computed property o'sha reaktiv state'ga bog'langan bo'ladi. Qachonki ushbu bog'liqlik o'zgarsa computed ham o'zgaradi

Tepadagi misolimizdan olib qaraydigan bo'lsak, totalPricecomputed property'da bir dona reactive dependency bor, bu albatta productsnomli ref. Qachonki productsda biror o'zgarish sodir bo'lsa, totalPriceavtomatik tarzda yangilanadi. Mazzami :)

Keshlash va odatiy method (funksiya)lardan farqi

Biror aqlli aytishi mumkin, oddiy funksiya orqali ham shu natijaga erishishimiz mumkinku deb:

<script setup>
import { ref } from "vue";

const products = ref([
  { id: 1, name: 'Xiami Mi Band 8 pro', price: 706000 },
  { id: 1, name: 'Konditsioner LG P09ED', price: 3899000 },
  { id: 1, name: 'Shampun', price: 35000},
]);

const calculateTotalPrice = () => {
  return products.value.reduce((sum, product) => sum + product.price, 9);
}
</script>
<template>
  <div>
    Jami: {{ calculateTotalPrice() }}
  </div>
</template>

Ushbu funksiya va tepada yozilgan computed property bir xil natija qaytaradi. Ammo eng katta farqi computed property reaktiv bog'liqliklari asosida keshlanadi. Ya'ni computed ichida ishlatilgan reaktiv bog'liqliklar o'zgarmasa computed property oldindan hisoblab qo'yilgan qiymatni qaytaradi. Oddiy funksiya esa har doim ishga tushaveradi.

Yana bir tomoni oddiy funksiya re-render vaqtida doim ishga tushadi, computed esa unday bo'lmaydi. Aytganimdek faqat o'zi bog'liq bo'lgan reactive state o'zgarsagina qayta ishga tushadi.

Ho'p nima bo'libti shunga, ishga tushsa tushar, mendan nima ketdi dersiz? Ha juda ham sodda ifodalarda bu bilinmasligi mumkin. Ammo juda ko'p elementlardan tashkil topgan massivlar yoki juda katta obyektlar ustida biror katta amal bajarilganda bu sezilarli darajada bilinishi mumkin. Shu framework'ni yozganlar ham bir nima bilar ))

computed keshlanishini quyidagi misol orqali tekshirib ko'rishimiz ham mumkin:

<script setup>
import { ref, computed, onBeforeUnmount } from "vue";

const ref1 = ref(0)
const ref2 = ref(0);
const ref3 = ref(0);

const doubleRef1 = computed(() => {
  console.log('Computed');
  return ref1.value * 2;
});

const doubleRef2 = () => {
  console.log('Function');
  return ref2.value * 2;
}

const intervalId = setInterval(() => {
  ref3.value ++;
});

onBeforeUnmoune(() => {
  clearInterval(intervalId);
});
</script>
<template>
  <div>
    <p>Computed: {{ doubleRef1 }}</p>
    <p>Function: {{ doubleRef2() }}</p>
    <p>Ref3: {{ ref3 }}</p>
  </div>

  </template>
computed vs methods

Rasmda ko'rib turganingizdek, console'ga Function so'zi 71 marta chiqqan, Computed so'zi esa faqat bir marta.

https://codesandbox.io/p/devbox/vue-dom-update-forked-ntzj9r

O'zgartiriladigan computed (Writable computed)

Ko'p hollarda computed property'lar biror reactive state orqali hosil qilinab faqat o'qish uchun bo'ladi. Lekin computed funksiyasiga setter funksiyasini ham berish orqali unga o'zgartirish (write qilish) imkoniyatini ham qo'shishimiz mumkin:

<script setup> 
import { ref, computed } from 'vue';  

const firstName = ref('John');
const lastName = ref('Doe');

const fullName = computed({  
  // getter  
  get() {  return firstName.value + ' ' + lastName.value  },  
  // setter  
  set(newValue) {  
    [firstName.value, lastName.value] = newValue.split(' ')  
  } 
});
 </script>

fullName.value = "Margaret Thatcher";

deb yozadigan bo'lsak firstName

Margaret, lastNameesa Thatcher qiymatlarini oladi.

Albatta bu juda oddiy misol bo'ldi. Keling real loyihada qo'l keladigan misollardan ham ko'ramiz:

Tasavvur qiling sizda Tab component bor, va siz tabning holatini, ya'ni qaysi tab panel aktiv bo'lib turganini route query'da saqlamoqchisiz. Shu holatda writable computed ishlatishimiz mumkin:

<script setup>
import { computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';

const route = useRoute();
const router = useRouter();

const currentTab = computed({
  get() {
    return route.query.tab?.toString();
  },
  set(newValue) {
    router.push({
      query: { ...route.query, tab: newValue || undefined }
    })
  }
});

const handleClick = (tab) => {
  currentTab.value = tab;
}
</script>
<template>
  <div>
    <el-tabs v-model="currentTab" @tab-click="handleClick"> 
      <el-tab-pane label="User" name="first">User</el-tab-pane> 
      <el-tab-pane label="Config" name="second">Config</el-tab-pane> 
      <el-tab-pane label="Role" name="third">Role</el-tab-pane> 
      <el-tab-pane label="Task" name="fourth">Task</el-tab-pane> 
    </el-tabs>
  </div>
</template>

Eng yaxshi amaliyotlar (Best practices)

Getter'lar side-effect'dan holi bo'lishi kerak. Ya'ni computed'ga berilgan getter funksiya ichida quyidagi amallar bajarilmasligi kerak:

  1. DOM'ni o'zgartirish
  2. async so'rovlar yuborish
  3. Boshqa reactive state'ni o'zgartirish

E'tiboringiz uchun rahmat!

https://t.me/mirjalol_norkulov