ΠŸΠ΅Ρ€Π΅ΠΉΡ‚ΠΈ ΠΊ основному содСрТимому

[Medium] πŸ“„ ΠžΡΠ½ΠΎΠ²Ρ‹ Vue ΠΈ API

1. ΠœΠΎΠΆΠ΅Ρ‚Π΅ Π»ΠΈ Π²Ρ‹ ΠΎΠΏΠΈΡΠ°Ρ‚ΡŒ основныС ΠΏΡ€ΠΈΠ½Ρ†ΠΈΠΏΡ‹ ΠΈ прСимущСства Ρ„Ρ€Π΅ΠΉΠΌΠ²ΠΎΡ€ΠΊΠ° Vue?​

ΠžΠΏΠΈΡˆΠΈΡ‚Π΅ основныС ΠΏΡ€ΠΈΠ½Ρ†ΠΈΠΏΡ‹ ΠΈ ΡΠΈΠ»ΡŒΠ½Ρ‹Π΅ стороны Vue.

ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ принципы​

Vue β€” прогрСссивный JavaScript-Ρ„Ρ€Π΅ΠΉΠΌΠ²ΠΎΡ€ΠΊ. Π•Π³ΠΎ ΠΊΠ»ΡŽΡ‡Π΅Π²Ρ‹Π΅ ΠΊΠΎΠ½Ρ†Π΅ΠΏΡ†ΠΈΠΈ Π²ΠΊΠ»ΡŽΡ‡Π°ΡŽΡ‚:

1. Virtual DOM​

Vue ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ Virtual DOM diffing для обновлСния Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΈΠ·ΠΌΠ΅Π½Ρ‘Π½Π½Ρ‹Ρ… частСй Ρ€Π΅Π°Π»ΡŒΠ½ΠΎΠ³ΠΎ DOM.

// упрощённая концСпция Virtual DOM
const vnode = {
tag: 'div',
props: { class: 'container' },
children: [
{ tag: 'h1', children: 'Hello' },
{ tag: 'p', children: 'World' },
],
};

2. Π Π΅Π°ΠΊΡ‚ΠΈΠ²Π½ΠΎΠ΅ связываниС данных​

Π Π΅Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅ автоматичСски ΠΎΠ±Π½ΠΎΠ²Π»ΡΡŽΡ‚ UI. Π‘ привязками Ρ„ΠΎΡ€ΠΌ (v-model) Π²Π²ΠΎΠ΄ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Ρ‚Π°ΠΊΠΆΠ΅ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΎΠ±Π½ΠΎΠ²Π»ΡΡ‚ΡŒ состояниС.

<template>
<input v-model="message" />
<p>{{ message }}</p>
</template>

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

const message = ref('Hello Vue');
</script>

3. ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π½Π°Ρ архитСктура​

UI раздСляСтся Π½Π° ΠΏΠ΅Ρ€Π΅ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌΡ‹Π΅, тСстируСмыС ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹ с ΠΈΠ·ΠΎΠ»ΠΈΡ€ΠΎΠ²Π°Π½Π½Ρ‹ΠΌΠΈ отвСтствСнностями.

<!-- Button.vue -->
<template>
<button @click="handleClick">
<slot></slot>
</button>
</template>

<script setup>
const emit = defineEmits(['click']);

const handleClick = () => emit('click');
</script>

4. Π₯ΡƒΠΊΠΈ ΠΆΠΈΠ·Π½Π΅Π½Π½ΠΎΠ³ΠΎ цикла​

Π₯ΡƒΠΊΠΈ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΡŽΡ‚ Π²Ρ‹ΠΏΠΎΠ»Π½ΡΡ‚ΡŒ Π»ΠΎΠ³ΠΈΠΊΡƒ Π½Π° этапах создания/монтирования/обновлСния/размонтирования.

<script setup>
import { onMounted, onUpdated, onUnmounted } from 'vue';

onMounted(() => {
console.log('ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ смонтирован');
});

onUpdated(() => {
console.log('ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ ΠΎΠ±Π½ΠΎΠ²Π»Ρ‘Π½');
});

onUnmounted(() => {
console.log('ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ Ρ€Π°Π·ΠΌΠΎΠ½Ρ‚ΠΈΡ€ΠΎΠ²Π°Π½');
});
</script>

5. БистСма дирСктив​

Π”ΠΈΡ€Π΅ΠΊΡ‚ΠΈΠ²Ρ‹ Vue ΠΏΡ€Π΅Π΄ΠΎΡΡ‚Π°Π²Π»ΡΡŽΡ‚ Π΄Π΅ΠΊΠ»Π°Ρ€Π°Ρ‚ΠΈΠ²Π½ΡƒΡŽ Π»ΠΎΠ³ΠΈΠΊΡƒ UI (v-if, v-for, v-bind, v-model ΠΈ Ρ‚.Π΄.).

<template>
<div v-if="isVisible">Π’ΠΈΠ΄ΠΈΠΌΠΎΠ΅ содСрТимоС</div>

<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>

<img :src="imageUrl" :alt="imageAlt" />

<input v-model="username" />
</template>

6. Бинтаксис ΡˆΠ°Π±Π»ΠΎΠ½ΠΎΠ²β€‹

Π¨Π°Π±Π»ΠΎΠ½Ρ‹ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°ΡŽΡ‚ ΠΈΠ½Ρ‚Π΅Ρ€ΠΏΠΎΠ»ΡΡ†ΠΈΡŽ ΠΈ выраТСния, сохраняя Ρ‡ΠΈΡ‚Π°Π΅ΠΌΠΎΡΡ‚ΡŒ Ρ€Π°Π·ΠΌΠ΅Ρ‚ΠΊΠΈ.

<template>
<div>
<p>{{ message }}</p>
<p>{{ count + 1 }}</p>
<p>{{ formatDate(date) }}</p>
</div>
</template>

Π‘ΠΈΠ»ΡŒΠ½Ρ‹Π΅ стороны Vue (часто сравниваСтся с React)​

1. Π‘ΠΎΠ»Π΅Π΅ Π½ΠΈΠ·ΠΊΠΈΠΉ ΠΏΠΎΡ€ΠΎΠ³ входа​

ΠžΠ΄Π½ΠΎΡ„Π°ΠΉΠ»ΠΎΠ²Ρ‹Π΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹ (template/script/style) ΠΈΠ½Ρ‚ΡƒΠΈΡ‚ΠΈΠ²Π½Ρ‹ для ΠΌΠ½ΠΎΠ³ΠΈΡ… ΠΊΠΎΠΌΠ°Π½Π΄.

2. ВстроСнныС Π΄Π΅ΠΊΠ»Π°Ρ€Π°Ρ‚ΠΈΠ²Π½Ρ‹Π΅ дирСктивы​

Π’ΠΈΠΏΠΈΡ‡Π½Ρ‹Π΅ Π·Π°Π΄Π°Ρ‡ΠΈ UI Π»Π°ΠΊΠΎΠ½ΠΈΡ‡Π½Ρ‹ с Π΄ΠΈΡ€Π΅ΠΊΡ‚ΠΈΠ²Π°ΠΌΠΈ.

3. ΠŸΡ€ΠΎΡΡ‚ΠΎΠ΅ двустороннСС связываниС форм​

v-model ΠΏΡ€Π΅Π΄Π»Π°Π³Π°Π΅Ρ‚ пСрвоклассный ΠΏΠ°Ρ‚Ρ‚Π΅Ρ€Π½ для синхронизации Π²Π²ΠΎΠ΄Π°.

4. Π§Ρ‘Ρ‚ΠΊΠΎΠ΅ Ρ€Π°Π·Π΄Π΅Π»Π΅Π½ΠΈΠ΅ шаблона ΠΈ логики​

НСкоторыС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ ΠΏΡ€Π΅Π΄ΠΏΠΎΡ‡ΠΈΡ‚Π°ΡŽΡ‚ структуру с ΠΏΡ€ΠΈΠΎΡ€ΠΈΡ‚Π΅Ρ‚ΠΎΠΌ шаблона ΠΏΠΎ ΡΡ€Π°Π²Π½Π΅Π½ΠΈΡŽ с ΠΏΠ°Ρ‚Ρ‚Π΅Ρ€Π½Π°ΠΌΠΈ, насыщСнными JSX.

5. ЦСлостная ΠΎΡ„ΠΈΡ†ΠΈΠ°Π»ΡŒΠ½Π°Ρ экосистСма​

Vue Router + Pinia + интСграция инструмСнтов Ρ…ΠΎΡ€ΠΎΡˆΠΎ согласованы.

2. ΠžΠ±ΡŠΡΡΠ½ΠΈΡ‚Π΅ использованиС v-model, v-bind ΠΈ v-html​

ΠžΠ±ΡŠΡΡΠ½ΠΈΡ‚Π΅ использованиС v-model, v-bind ΠΈ v-html.

v-model: двустороннСС связываниС для элСмСнтов форм​

<template>
<div>
<input v-model="message" />
<p>Π‘ΠΎΠΎΠ±Ρ‰Π΅Π½ΠΈΠ΅: {{ message }}</p>

<input type="checkbox" v-model="checked" />
<p>ΠžΡ‚ΠΌΠ΅Ρ‡Π΅Π½ΠΎ: {{ checked }}</p>

<select v-model="selected">
<option value="A">Π’Π°Ρ€ΠΈΠ°Π½Ρ‚ A</option>
<option value="B">Π’Π°Ρ€ΠΈΠ°Π½Ρ‚ B</option>
</select>
<p>Π’Ρ‹Π±Ρ€Π°Π½ΠΎ: {{ selected }}</p>
</div>
</template>

<script>
export default {
data() {
return {
message: '',
checked: false,
selected: 'A',
};
},
};
</script>

ΠœΠΎΠ΄ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€Ρ‹ v-model​

<input v-model.lazy="msg" />
<input v-model.number="age" type="number" />
<input v-model.trim="msg" />

v-bind: динамичСская привязка атрибутов​

<template>
<div>
<div :class="{ active: isActive, 'text-danger': hasError }">ДинамичСский класс</div>

<div :style="{ color: textColor, fontSize: fontSize + 'px' }">ДинамичСский ΡΡ‚ΠΈΠ»ΡŒ</div>

<img :src="imageUrl" :alt="imageAlt" />

<a :href="linkUrl">ΠŸΠ΅Ρ€Π΅ΠΉΡ‚ΠΈ ΠΏΠΎ ссылкС</a>

<div :data-id="userId" :data-name="userName"></div>
</div>
</template>

<script>
export default {
data() {
return {
isActive: true,
hasError: false,
textColor: 'red',
fontSize: 16,
imageUrl: 'https://example.com/image.jpg',
imageAlt: 'ОписаниС изобраТСния',
linkUrl: 'https://example.com',
userId: 123,
userName: 'John',
};
},
};
</script>

Π‘ΠΎΠΊΡ€Π°Ρ‰Π΅Π½ΠΈΠ΅ v-bind​

<img v-bind:src="imageUrl" />
<img :src="imageUrl" />
<div v-bind="objectOfAttrs"></div>

v-html: Ρ€Π΅Π½Π΄Π΅Ρ€ΠΈΠ½Π³ сырой HTML-строки​

<template>
<div>
<p>{{ rawHtml }}</p>
<p v-html="rawHtml"></p>
</div>
</template>

<script>
export default {
data() {
return {
rawHtml: '<span style="color: red">ΠšΡ€Π°ΡΠ½Ρ‹ΠΉ тСкст</span>',
};
},
};
</script>

ΠŸΡ€Π΅Π΄ΡƒΠΏΡ€Π΅ΠΆΠ΄Π΅Π½ΠΈΠ΅ ΠΎ бСзопасности​

Никогда Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ v-html Π½Π°ΠΏΡ€ΡΠΌΡƒΡŽ для Π½Π΅ΠΏΡ€ΠΎΠ²Π΅Ρ€Π΅Π½Π½ΠΎΠ³ΠΎ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΎΠ³ΠΎ Π²Π²ΠΎΠ΄Π° (риск XSS).

<!-- нСбСзопасно -->
<div v-html="userProvidedContent"></div>

<!-- бСзопаснСС: санитизированноС содСрТимоС -->
<div v-html="sanitizedHtml"></div>

Π‘ΠΎΠ»Π΅Π΅ бСзопасный ΠΏΠΎΠ΄Ρ…ΠΎΠ΄ с санитайзСром​

<script>
import DOMPurify from 'dompurify';

export default {
data() {
return {
userInput: '<img src=x onerror=alert("XSS")>',
};
},
computed: {
sanitizedHtml() {
return DOMPurify.sanitize(this.userInput);
},
},
};
</script>

ΠšΡ€Π°Ρ‚ΠΊΠΎΠ΅ сравнСниС​

Π”ΠΈΡ€Π΅ΠΊΡ‚ΠΈΠ²Π°ΠΠ°Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅Π‘ΠΎΠΊΡ€Π°Ρ‰Π΅Π½ΠΈΠ΅ΠŸΡ€ΠΈΠΌΠ΅Ρ€
v-modelДвустороннСС связываниС формНСт<input v-model="msg">
v-bindΠžΠ΄Π½ΠΎΡΡ‚ΠΎΡ€ΠΎΠ½Π½Π΅Π΅ связываниС Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚ΠΎΠ²:<img :src="url">
v-htmlΠ Π΅Π½Π΄Π΅Ρ€ΠΈΠ½Π³ сырого HTMLНСт<div v-html="html"></div>

3. Как ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ доступ ΠΊ HTML-элСмСнтам (Template Refs)?​

Как ΠΌΠ°Π½ΠΈΠΏΡƒΠ»ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ HTML-элСмСнтами Π² Vue (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€, ΡƒΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ фокус Π½Π° input)?

Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ template refs вмСсто document.querySelector Π² ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π°Ρ….

Options API (Vue 2 / Vue 3)​

<template>
<div>
<input ref="inputElement" />
<button @click="focusInput">Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ фокус</button>
</div>
</template>

<script>
export default {
methods: {
focusInput() {
this.$refs.inputElement.focus();
},
},
mounted() {
console.log(this.$refs.inputElement);
},
};
</script>

Composition API (Vue 3)​

<template>
<div>
<input ref="inputElement" />
<button @click="focusInput">Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ фокус</button>
</div>
</template>

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

const inputElement = ref(null);

const focusInput = () => {
inputElement.value?.focus();
};

onMounted(() => {
console.log(inputElement.value);
});
</script>

ЗамСчания:

  • Имя ref Π² шаблонС Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΡΠΎΠ²ΠΏΠ°Π΄Π°Ρ‚ΡŒ с ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎΠΉ Π² скриптС
  • Доступ послС монтирования (onMounted / mounted)
  • Π’Π½ΡƒΡ‚Ρ€ΠΈ v-for refs становятся массивами

4. Π’ Ρ‡Ρ‘ΠΌ Ρ€Π°Π·Π½ΠΈΡ†Π° ΠΌΠ΅ΠΆΠ΄Ρƒ v-show ΠΈ v-if?​

ΠžΠ±ΡŠΡΡΠ½ΠΈΡ‚Π΅ различия ΠΌΠ΅ΠΆΠ΄Ρƒ v-show ΠΈ v-if.

Бходство​

Оба ΡƒΠΏΡ€Π°Π²Π»ΡΡŽΡ‚ Π²ΠΈΠ΄ΠΈΠΌΠΎΡΡ‚ΡŒΡŽ Π½Π° основС условий.

<template>
<div v-if="isVisible">Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ v-if</div>
<div v-show="isVisible">Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ v-show</div>
</template>

Различия​

1) ПовСдСниС DOM​

  • v-if: ΠΌΠΎΠ½Ρ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅/Ρ€Π°Π·ΠΌΠΎΠ½Ρ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ ΡƒΠ·Π»Π°
  • v-show: всСгда смонтирован; ΠΏΠ΅Ρ€Π΅ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ CSS display

2) ΠŸΡ€ΠΎΡ„ΠΈΠ»ΡŒ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈβ€‹

  • v-if: низкая Π½Π°Ρ‡Π°Π»ΡŒΠ½Π°Ρ ΡΡ‚ΠΎΠΈΠΌΠΎΡΡ‚ΡŒ ΠΏΡ€ΠΈ false, высокая ΡΡ‚ΠΎΠΈΠΌΠΎΡΡ‚ΡŒ ΠΏΠ΅Ρ€Π΅ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ
  • v-show: высокая Π½Π°Ρ‡Π°Π»ΡŒΠ½Π°Ρ ΡΡ‚ΠΎΠΈΠΌΠΎΡΡ‚ΡŒ, низкая ΡΡ‚ΠΎΠΈΠΌΠΎΡΡ‚ΡŒ ΠΏΠ΅Ρ€Π΅ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ

3) ВлияниС Π½Π° ΠΆΠΈΠ·Π½Π΅Π½Π½Ρ‹ΠΉ цикл​

  • v-if запускаСт ΠΏΠΎΠ»Π½Ρ‹ΠΉ ΠΆΠΈΠ·Π½Π΅Π½Π½Ρ‹ΠΉ Ρ†ΠΈΠΊΠ» Π΄ΠΎΡ‡Π΅Ρ€Π½ΠΈΡ… элСмСнтов ΠΏΡ€ΠΈ ΠΏΠ΅Ρ€Π΅ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ
  • v-show Π½Π΅ Ρ€Π°Π·ΠΌΠΎΠ½Ρ‚ΠΈΡ€ΡƒΠ΅Ρ‚; Π½Π΅Ρ‚ монтирования/размонтирования ΠΏΡ€ΠΈ ΠΏΠ΅Ρ€Π΅ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈ

4) Π‘Ρ‚ΠΎΠΈΠΌΠΎΡΡ‚ΡŒ Π½Π°Ρ‡Π°Π»ΡŒΠ½ΠΎΠ³ΠΎ рСндСра​

Для тяТёлых ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ΠΎΠ², ΠΈΠ·Π½Π°Ρ‡Π°Π»ΡŒΠ½ΠΎ скрытых:

  • v-if="false": ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ Π½Π΅ рСндСрится
  • v-show="false": ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ рСндСрится, Π½ΠΎ скрыт

5) ΠšΠΎΠΌΠ±ΠΈΠ½Π°Ρ†ΠΈΠΈ дирСктив​

  • v-if ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ v-else-if / v-else
  • v-show β€” Π½Π΅Ρ‚

Когда ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ каТдый​

Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ v-if, когда​

  1. УсловиС мСняСтся Ρ€Π΅Π΄ΠΊΠΎ
  2. ΠΠ°Ρ‡Π°Π»ΡŒΠ½ΠΎΠ΅ false Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΠΈΠ·Π±Π΅ΠΆΠ°Ρ‚ΡŒ стоимости Ρ€Π΅Π½Π΄Π΅Ρ€ΠΈΠ½Π³Π°
  3. Π’Π°ΠΌ Π½ΡƒΠΆΠ½Ρ‹ условныС Π²Π΅Ρ‚ΠΊΠΈ с v-else
  4. НуТны ΠΏΠΎΠ±ΠΎΡ‡Π½Ρ‹Π΅ эффСкты монтирования/размонтирования

Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ v-show, когда​

  1. Π’ΠΈΠ΄ΠΈΠΌΠΎΡΡ‚ΡŒ ΠΏΠ΅Ρ€Π΅ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ΡΡ часто
  2. ΠšΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΎΡΡ‚Π°Π²Π°Ρ‚ΡŒΡΡ смонтированным для сохранСния Π²Π½ΡƒΡ‚Ρ€Π΅Π½Π½Π΅Π³ΠΎ состояния
  3. ΠŸΠΎΠ²Ρ‚ΠΎΡ€Π½ΠΎΠ΅ ΠΌΠΎΠ½Ρ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ Π½Π΅ трСбуСтся

Бводная таблица​

Π₯арактСристикаv-ifv-show
ΠΠ°Ρ‡Π°Π»ΡŒΠ½Π°Ρ ΡΡ‚ΠΎΠΈΠΌΠΎΡΡ‚ΡŒΠΠΈΠΆΠ΅ (ΠΏΡ€ΠΈ false)Π’Ρ‹ΡˆΠ΅ (всСгда рСндСрится)
Π‘Ρ‚ΠΎΠΈΠΌΠΎΡΡ‚ΡŒ ΠΏΠ΅Ρ€Π΅ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡΠ’Ρ‹ΡˆΠ΅ΠΠΈΠΆΠ΅
Lifecycle ΠΏΡ€ΠΈ ΠΏΠ΅Ρ€Π΅ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΈΠ”Π°ΠΠ΅Ρ‚
Π›ΡƒΡ‡ΡˆΠ΅ дляРСдких ΠΏΠ΅Ρ€Π΅ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΉΠ§Π°ΡΡ‚Ρ‹Ρ… ΠΏΠ΅Ρ€Π΅ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠΉ

ΠœΠ½Π΅ΠΌΠΎΠ½ΠΈΠΊΠ°β€‹

  • v-if: Β«Ρ€Π΅Π½Π΄Π΅Ρ€ΠΈΡ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΡ€ΠΈ нСобходимости»
  • v-show: Β«Ρ€Π΅Π½Π΄Π΅Ρ€ΠΈΡ‚ΡŒ ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π·, ΡΠΊΡ€Ρ‹Π²Π°Ρ‚ΡŒ/ΠΏΠΎΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ Ρ‡Π΅Ρ€Π΅Π· CSSΒ»

5. Π’ Ρ‡Ρ‘ΠΌ Ρ€Π°Π·Π½ΠΈΡ†Π° ΠΌΠ΅ΠΆΠ΄Ρƒ computed ΠΈ watch?​

Π’ Ρ‡Ρ‘ΠΌ Ρ€Π°Π·Π½ΠΈΡ†Π° ΠΌΠ΅ΠΆΠ΄Ρƒ computed ΠΈ watch?

Оба Ρ€Π΅Π°Π³ΠΈΡ€ΡƒΡŽΡ‚ Π½Π° измСнСния состояния, Π½ΠΎ Ρ€Π΅ΡˆΠ°ΡŽΡ‚ Ρ€Π°Π·Π½Ρ‹Π΅ Π·Π°Π΄Π°Ρ‡ΠΈ.

computed​

ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ характСристики​

  1. ВычисляСт Π½ΠΎΠ²Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅ ΠΈΠ· ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰Π΅Π³ΠΎ Ρ€Π΅Π°ΠΊΡ‚ΠΈΠ²Π½ΠΎΠ³ΠΎ состояния
  2. ΠšΠ΅ΡˆΠΈΡ€ΡƒΠ΅Ρ‚ΡΡ Π΄ΠΎ измСнСния зависимостСй
  3. Π‘ΠΈΠ½Ρ…Ρ€ΠΎΠ½Π½Ρ‹ΠΉ ΠΈ ΠΎΡ€ΠΈΠ΅Π½Ρ‚ΠΈΡ€ΠΎΠ²Π°Π½ Π½Π° Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΠΎΠ΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅
  4. ΠΠ°ΠΏΡ€ΡΠΌΡƒΡŽ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Π² шаблонС

Π’ΠΈΠΏΠΈΡ‡Π½Ρ‹Π΅ случаи ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Π½ΠΈΡβ€‹

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

const firstName = ref('John');
const lastName = ref('Doe');
const email = ref('JOHN@EXAMPLE.COM');
const cart = ref([
{ id: 1, name: 'Apple', price: 2, quantity: 3 },
{ id: 2, name: 'Banana', price: 1, quantity: 5 },
]);
const searchText = ref('');
const items = ref([
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Cherry' },
]);

const fullName = computed(() => `${firstName.value} ${lastName.value}`);
const emailLowerCase = computed(() => email.value.toLowerCase());
const cartTotal = computed(() =>
cart.value.reduce((total, item) => total + item.price * item.quantity, 0)
);
const filteredItems = computed(() =>
!searchText.value
? items.value
: items.value.filter((item) =>
item.name.toLowerCase().includes(searchText.value.toLowerCase())
)
);
</script>

ΠŸΡ€Π΅ΠΈΠΌΡƒΡ‰Π΅ΡΡ‚Π²ΠΎ ΠΊΠ΅ΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΡβ€‹

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

const items = ref(Array.from({ length: 1000 }, (_, index) => index));

const expensiveComputed = computed(() => {
console.log('computed выполняСтся Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΡ€ΠΈ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΈ зависимости');
return items.value.reduce((sum, item) => sum + item, 0);
});

const expensiveMethod = () => {
console.log('ΠΌΠ΅Ρ‚ΠΎΠ΄ выполняСтся ΠΏΡ€ΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠΌ Π²Ρ‹Π·ΠΎΠ²Π΅');
return items.value.reduce((sum, item) => sum + item, 0);
};
</script>

Π€ΠΎΡ€ΠΌΠ° getter + setter​

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

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

const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`;
},
set(newValue) {
const names = newValue.split(' ');
firstName.value = names[0] ?? '';
lastName.value = names[names.length - 1] ?? '';
},
});
</script>

watch​

ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ характСристики​

  1. Π―Π²Π½ΠΎ Π½Π°Π±Π»ΡŽΠ΄Π°Π΅Ρ‚ Π·Π° источником(Π°ΠΌΠΈ)
  2. ΠŸΡ€Π΅Π΄Π½Π°Π·Π½Π°Ρ‡Π΅Π½ для ΠΏΠΎΠ±ΠΎΡ‡Π½Ρ‹Ρ… эффСктов
  3. ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ асинхронныС Ρ€Π°Π±ΠΎΡ‡ΠΈΠ΅ процСссы
  4. ΠœΠΎΠΆΠ΅Ρ‚ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ доступ ΠΊ newValue ΠΈ oldValue

Π’ΠΈΠΏΠΈΡ‡Π½Ρ‹Π΅ случаи ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Π½ΠΈΡβ€‹

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

const searchQuery = ref('');
const searchResults = ref([]);
const isSearching = ref(false);

const username = ref('');
const usernameError = ref('');

const content = ref('');
const isSaving = ref(false);
const lastSaved = ref(null);

let searchTimer = null;
let saveTimer = null;

// 1) поиск с Π·Π°Π΄Π΅Ρ€ΠΆΠΊΠΎΠΉ
watch(searchQuery, (newQuery, oldQuery) => {
clearTimeout(searchTimer);

if (!newQuery) {
searchResults.value = [];
return;
}

isSearching.value = true;
searchTimer = setTimeout(async () => {
try {
const response = await fetch(`/api/users?q=${newQuery}`);
searchResults.value = await response.json();
} finally {
isSearching.value = false;
}
}, 500);
});

// 2) ΠΏΠΎΠ±ΠΎΡ‡Π½Ρ‹ΠΉ эффСкт Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ
watch(username, (newUsername) => {
if (newUsername.length < 3) {
usernameError.value = 'Имя ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ Π½Π΅ ΠΌΠ΅Π½Π΅Π΅ 3 символов';
} else if (newUsername.length > 20) {
usernameError.value = 'Имя ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ Π½Π΅ Π±ΠΎΠ»Π΅Π΅ 20 символов';
} else if (!/^[a-zA-Z0-9_]+$/.test(newUsername)) {
usernameError.value =
'Имя ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ ΠΌΠΎΠΆΠ΅Ρ‚ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π±ΡƒΠΊΠ²Ρ‹, Ρ†ΠΈΡ„Ρ€Ρ‹ ΠΈ Π½ΠΈΠΆΠ½Π΅Π΅ ΠΏΠΎΠ΄Ρ‡Ρ‘Ρ€ΠΊΠΈΠ²Π°Π½ΠΈΠ΅';
} else {
usernameError.value = '';
}
});

// 3) ΠΏΠΎΠ±ΠΎΡ‡Π½Ρ‹ΠΉ эффСкт автосохранСния
watch(content, (newContent) => {
clearTimeout(saveTimer);

saveTimer = setTimeout(async () => {
isSaving.value = true;
try {
await fetch('/api/save', {
method: 'POST',
body: JSON.stringify({ content: newContent }),
});
lastSaved.value = new Date().toLocaleTimeString();
} finally {
isSaving.value = false;
}
}, 1000);
});

onBeforeUnmount(() => {
clearTimeout(searchTimer);
clearTimeout(saveTimer);
});
</script>

ΠžΠΏΡ†ΠΈΠΈ watch​

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

const user = ref({
name: 'John',
profile: { age: 30, city: 'Taipei' },
});
const items = ref([1, 2, 3]);

// immediate: Π²Ρ‹ΠΏΠΎΠ»Π½ΠΈΡ‚ΡŒ сразу
watch(
() => user.value.name,
(newName, oldName) => {
console.log(`Имя измСнилось с ${oldName} на ${newName}`);
},
{ immediate: true }
);

// deep: ΠΎΡ‚ΡΠ»Π΅ΠΆΠΈΠ²Π°Ρ‚ΡŒ Π²Π»ΠΎΠΆΠ΅Π½Π½Ρ‹Π΅ ΠΌΡƒΡ‚Π°Ρ†ΠΈΠΈ
watch(
user,
(newUser) => {
console.log('Π²Π»ΠΎΠΆΠ΅Π½Π½Ρ‹ΠΉ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ user ΠΈΠ·ΠΌΠ΅Π½Ρ‘Π½', newUser);
},
{ deep: true }
);

// flush: ΡƒΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ Ρ‚Π°ΠΉΠΌΠΈΠ½Π³ΠΎΠΌ (pre/post/sync)
watch(
items,
() => {
console.log('items измСнился');
},
{ flush: 'post' }
);
</script>

НаблюдСниС Π·Π° нСсколькими источниками​

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

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

watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
console.log(`Имя измСнилось с ${oldFirst} ${oldLast} на ${newFirst} ${newLast}`);
});
</script>

computed vs watch​

Π₯арактСристикаcomputedwatch
Основная Ρ†Π΅Π»ΡŒΠ’Ρ‹Ρ‡ΠΈΡΠ»Π΅Π½ΠΈΠ΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΡΠŸΠΎΠ±ΠΎΡ‡Π½Ρ‹ΠΉ эффСкт ΠΏΡ€ΠΈ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΈ
Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΠΎΠ΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ΠžΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΠΠ΅ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ/Π½Π΅Ρ‚
ΠšΠ΅ΡˆΠ”Π°ΠΠ΅Ρ‚
ΠžΡ‚ΡΠ»Π΅ΠΆΠΈΠ²Π°Π½ΠΈΠ΅ зависимостСйАвтоматичСскоСЯвный источник
АсинхронныС ΠΏΠΎΠ±ΠΎΡ‡Π½Ρ‹Π΅ эффСктыНСтДа
Π‘Ρ‚Π°Ρ€ΠΎΠ΅/Π½ΠΎΠ²ΠΎΠ΅ значСнияНСтДа
ΠŸΡ€ΡΠΌΠΎΠ΅ использованиС Π² ΡˆΠ°Π±Π»ΠΎΠ½Π΅Π”Π°ΠΠ΅Ρ‚

ΠŸΡ€Π°Π²ΠΈΠ»ΠΎβ€‹

  • computed вычисляСт Π΄Π°Π½Π½Ρ‹Π΅
  • watch выполняСт дСйствия

ΠŸΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎΠ΅/Π½Π΅ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎΠ΅ сравнСниС​

ΠΠ΅ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎβ€‹

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

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

watch(firstName, (newFirst) => {
fullName.value = `${newFirst} ${lastName.value}`;
});

watch(lastName, (newLast) => {
fullName.value = `${firstName.value} ${newLast}`;
});
</script>

ΠŸΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎβ€‹

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

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

const fullName = computed(() => `${firstName.value} ${lastName.value}`);
</script>

ΠŸΡ€Π°ΠΊΡ‚ΠΈΠΊΠ°: Π²Ρ‹Ρ‡ΠΈΡΠ»ΠΈΡ‚ΡŒ x * y​

Π”Π°Π½ΠΎ x = 0, y = 5, ΠΊΠ½ΠΎΠΏΠΊΠ° ΡƒΠ²Π΅Π»ΠΈΡ‡ΠΈΠ²Π°Π΅Ρ‚ x Π½Π° 1 ΠΏΡ€ΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠΌ ΠΊΠ»ΠΈΠΊΠ΅.

РСшСниС A: computed (рСкомСндуСтся)​

<template>
<div>
<p>X: {{ x }}, Y: {{ y }}</p>
<p>Π Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ (X * Y): {{ result }}</p>
<button @click="x++">Π£Π²Π΅Π»ΠΈΡ‡ΠΈΡ‚ΡŒ X</button>
</div>
</template>

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

const x = ref(0);
const y = ref(5);

const result = computed(() => x.value * y.value);
</script>

РСшСниС B: watch (Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚, Π½ΠΎ Π±ΠΎΠ»Π΅Π΅ многословно)​

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

const x = ref(0);
const y = ref(5);
const result = ref(0);

watch(
[x, y],
([newX, newY]) => {
result.value = newX * newY;
},
{ immediate: true }
);
</script>

Π‘ΠΏΡ€Π°Π²ΠΎΡ‡Π½Ρ‹Π΅ матСриалы​