Vai al contenuto principale

[Medium] 📄 Vue Base & API

1. Puoi descrivere i principi fondamentali e i vantaggi del framework Vue?

Descrivi i principi fondamentali e i punti di forza di Vue.

Principi fondamentali

Vue è un framework JavaScript progressivo. I suoi concetti fondamentali includono:

1. Virtual DOM

Vue utilizza il diffing del Virtual DOM per aggiornare solo le parti modificate del DOM reale.

// concetto semplificato di Virtual DOM
const vnode = {
tag: 'div',
props: { class: 'container' },
children: [
{ tag: 'h1', children: 'Hello' },
{ tag: 'p', children: 'World' },
],
};

2. Binding reattivo dei dati

I dati reattivi aggiornano automaticamente la UI. Con i binding dei form (v-model), l'input della UI può anche aggiornare lo stato.

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

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

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

3. Architettura basata su componenti

La UI è suddivisa in componenti riutilizzabili e testabili con responsabilità isolate.

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

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

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

4. Lifecycle hooks

Gli hooks permettono di eseguire logica nei momenti di creazione/montaggio/aggiornamento/smontaggio.

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

onMounted(() => {
console.log('Componente montato');
});

onUpdated(() => {
console.log('Componente aggiornato');
});

onUnmounted(() => {
console.log('Componente smontato');
});
</script>

5. Sistema di direttive

Le direttive Vue forniscono logica UI dichiarativa (v-if, v-for, v-bind, v-model, ecc.).

<template>
<div v-if="isVisible">Contenuto visibile</div>

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

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

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

6. Sintassi dei template

I template supportano interpolazione ed espressioni mantenendo il markup leggibile.

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

Punti di forza di Vue (spesso confrontati con React)

1. Costo di onboarding inferiore

I single-file components (template/script/style) sono intuitivi per molti team.

2. Direttive dichiarative integrate

Le attività UI comuni sono concise con le direttive.

3. Binding bidirezionale facile per i form

v-model offre un pattern di prima classe per la sincronizzazione degli input.

4. Chiara separazione tra template e logica

Alcuni team preferiscono la struttura template-first rispetto ai pattern pesanti in JSX.

5. Ecosistema ufficiale coeso

Vue Router + Pinia + strumenti di integrazione sono ben allineati.

2. Spiega l'utilizzo di v-model, v-bind e v-html

Spiega l'utilizzo di v-model, v-bind e v-html.

v-model: binding bidirezionale per controlli form

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

<input type="checkbox" v-model="checked" />
<p>Selezionato: {{ checked }}</p>

<select v-model="selected">
<option value="A">Opzione A</option>
<option value="B">Opzione B</option>
</select>
<p>Selezionato: {{ selected }}</p>
</div>
</template>

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

Modificatori di v-model

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

v-bind: binding dinamico degli attributi

<template>
<div>
<div :class="{ active: isActive, 'text-danger': hasError }">Classe dinamica</div>

<div :style="{ color: textColor, fontSize: fontSize + 'px' }">Stile dinamico</div>

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

<a :href="linkUrl">Vai al link</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: 'Descrizione immagine',
linkUrl: 'https://example.com',
userId: 123,
userName: 'John',
};
},
};
</script>

Abbreviazione di v-bind

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

v-html: renderizzare stringhe HTML raw

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

<script>
export default {
data() {
return {
rawHtml: '<span style="color: red">Testo rosso</span>',
};
},
};
</script>

Avviso di sicurezza

Non usare mai v-html direttamente su input utente non affidabile (rischio XSS).

<!-- non sicuro -->
<div v-html="userProvidedContent"></div>

<!-- più sicuro: contenuto sanitizzato -->
<div v-html="sanitizedHtml"></div>

Approccio più sicuro con sanitizer

<script>
import DOMPurify from 'dompurify';

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

Confronto rapido

DirettivaScopoAbbreviazioneEsempio
v-modelBinding bidirezionale formNessuna<input v-model="msg">
v-bindBinding unidirezionale attributi:<img :src="url">
v-htmlRenderizzare HTML rawNessuna<div v-html="html"></div>

3. Come accedere agli elementi HTML (Template Refs)?

Come manipolare gli elementi HTML in Vue (ad esempio dare il focus a un input)?

Usare i template refs invece di document.querySelector nei componenti.

Options API (Vue 2 / Vue 3)

<template>
<div>
<input ref="inputElement" />
<button @click="focusInput">Focus Input</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">Focus Input</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>

Note:

  • il nome ref nel template deve corrispondere alla variabile nello script
  • accedere dopo il mount (onMounted / mounted)
  • all'interno di v-for, i refs diventano array

4. Spiega la differenza tra v-show e v-if

Spiega le differenze tra v-show e v-if.

Somiglianza

Entrambi controllano la visibilità in base a condizioni.

<template>
<div v-if="isVisible">Usando v-if</div>
<div v-show="isVisible">Usando v-show</div>
</template>

Differenze

1) Comportamento DOM

  • v-if: monta/smonta il nodo
  • v-show: sempre montato; alterna il CSS display

2) Profilo prestazionale

  • v-if: costo iniziale inferiore quando è false, costo di alternanza superiore
  • v-show: costo iniziale superiore, costo di alternanza inferiore

3) Impatto sul lifecycle

  • v-if attiva l'intero lifecycle dei figli all'alternanza
  • v-show non smonta; nessun mount/unmount all'alternanza

4) Costo del render iniziale

Per componenti pesanti inizialmente nascosti:

  • v-if="false": il componente non viene renderizzato
  • v-show="false": il componente viene renderizzato ma nascosto

5) Combinazioni di direttive

  • v-if supporta v-else-if / v-else
  • v-show no

Quando usare ciascuno

Usa v-if quando

  1. la condizione cambia raramente
  2. un false iniziale dovrebbe evitare il costo di rendering
  3. hai bisogno di rami condizionali con v-else
  4. gli effetti collaterali di mount/unmount sono desiderati

Usa v-show quando

  1. la visibilità viene alternata frequentemente
  2. il componente dovrebbe rimanere montato per preservare lo stato interno
  3. il remounting del lifecycle non è necessario

Tabella riepilogativa

Caratteristicav-ifv-show
Costo inizialeInferiore (quando false)Superiore (renderizza sempre)
Costo di alternanzaSuperioreInferiore
Lifecycle all'alternanzaNo
Migliore perAlternanze rareAlternanze frequenti

Suggerimento mnemonico

  • v-if: "renderizza solo quando necessario"
  • v-show: "renderizza una volta, nascondi/mostra via CSS"

5. Qual è la differenza tra computed e watch?

Qual è la differenza tra computed e watch?

Entrambi reagiscono ai cambiamenti di stato ma risolvono problemi diversi.

computed

Caratteristiche principali

  1. deriva nuovi dati dallo stato reattivo esistente
  2. memorizzato in cache finché le dipendenze non cambiano
  3. sincrono e orientato al valore di ritorno
  4. utilizzabile direttamente nel template

Casi d'uso tipici

<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>

Vantaggio della cache

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

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

const expensiveComputed = computed(() => {
console.log('computed eseguito solo quando la dipendenza cambia');
return items.value.reduce((sum, item) => sum + item, 0);
});

const expensiveMethod = () => {
console.log('metodo eseguito ad ogni chiamata');
return items.value.reduce((sum, item) => sum + item, 0);
};
</script>

Forma 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

Caratteristiche principali

  1. osserva esplicitamente sorgente/i
  2. destinato agli effetti collaterali
  3. supporta workflow asincroni
  4. può accedere a newValue e oldValue

Casi d'uso tipici

<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) ricerca con debounce
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) effetto collaterale di validazione
watch(username, (newUsername) => {
if (newUsername.length < 3) {
usernameError.value = 'Il nome utente deve avere almeno 3 caratteri';
} else if (newUsername.length > 20) {
usernameError.value = 'Il nome utente deve avere al massimo 20 caratteri';
} else if (!/^[a-zA-Z0-9_]+$/.test(newUsername)) {
usernameError.value =
'Il nome utente può includere solo lettere, numeri e underscore';
} else {
usernameError.value = '';
}
});

// 3) effetto collaterale di salvataggio automatico
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>

Opzioni di 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: eseguire subito una volta
watch(
() => user.value.name,
(newName, oldName) => {
console.log(`Nome cambiato da ${oldName} a ${newName}`);
},
{ immediate: true }
);

// deep: tracciare mutazioni annidate
watch(
user,
(newUser) => {
console.log('oggetto utente annidato cambiato', newUser);
},
{ deep: true }
);

// flush: controllare il timing (pre/post/sync)
watch(
items,
() => {
console.log('items cambiato');
},
{ flush: 'post' }
);
</script>

Osservare sorgenti multiple

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

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

watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
console.log(`Nome cambiato da ${oldFirst} ${oldLast} a ${newFirst} ${newLast}`);
});
</script>

computed vs watch

Caratteristicacomputedwatch
Obiettivo principalederivare un valoreeffetto collaterale al cambiamento
Valore di ritornorichiestoopzionale/nessuno
Cacheno
Tracciamento dipendenzeautomaticosorgente esplicita
Effetti collaterali asincronino
Valori vecchi/nuovino
Uso diretto nel templateno

Regola pratica

  • computed calcola dati
  • watch esegue azioni

Confronto corretto/scorretto

Scorretto ❌

<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>

Corretto ✅

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

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

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

Esercizio: calcolare x * y

Dati x = 0, y = 5, e un pulsante che incrementa x di 1 ad ogni click.

Soluzione A: computed (raccomandata)

<template>
<div>
<p>X: {{ x }}, Y: {{ y }}</p>
<p>Risultato (X * Y): {{ result }}</p>
<button @click="x++">Incrementa 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>

Soluzione B: watch (funziona ma più verbosa)

<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>

Riferimenti