Vue.js est un framework JavaScript progressif pour construire des interfaces utilisateur. Composants réutilisables, réactivité, Vue Router pour SPA, Vuex/Pinia pour state management. Vue 3 apporte Composition API et meilleures performances.
# Créer projet avec Vite (rapide)
npm create vite@latest mon-app -- --template vue
cd mon-app
npm install
npm run dev
# Ou avec Vue CLI
npm install -g @vue/cli
vue create mon-app
cd mon-app
npm run serve
# Ajouter Vue Router
npm install vue-router@4
# Ajouter Pinia (state management)
npm install pinia
<template>
<div class="hello">
<h1>{{ message }}</h1>
<button @click="increment">Count: {{ count }}</button>
<p>Double: {{ doubleCount }}</p>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
message: 'Bonjour Vue!',
count: 0
}
},
computed: {
doubleCount() {
return this.count * 2
}
},
methods: {
increment() {
this.count++
}
}
}
</script>
<style scoped>
.hello {
padding: 20px;
}
h1 {
color: #42b983;
}
</style>
<template>
<div>
<h1>{{ message }}</h1>
<button @click="increment">Count: {{ count }}</button>
<p>Double: {{ doubleCount }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const message = ref('Bonjour Vue!')
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
</script>
<template>
<!-- v-bind : lier attribut -->
<img :src="imageUrl" :alt="imageAlt">
<div :class="{ active: isActive }"></div>
<div :style="{ color: textColor, fontSize: size + 'px' }"></div>
<!-- v-on @ : événements -->
<button @click="handleClick">Cliquer</button>
<input @input="handleInput" @keyup.enter="submit">
<form @submit.prevent="onSubmit"></form>
<!-- v-model : two-way binding -->
<input v-model="username" type="text">
<textarea v-model="message"></textarea>
<input v-model="checked" type="checkbox">
<select v-model="selected">
<option value="a">A</option>
<option value="b">B</option>
</select>
<!-- v-if / v-else / v-show -->
<p v-if="isVisible">Visible</p>
<p v-else>Caché</p>
<p v-show="showElement">Toggle display</p>
<!-- v-for : boucles -->
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
<!-- v-for avec index -->
<li v-for="(item, index) in items" :key="item.id">
{{ index }}: {{ item.name }}
</li>
<!-- v-for sur objet -->
<div v-for="(value, key) in user" :key="key">
{{ key }}: {{ value }}
</div>
</template>
<!-- UserCard.vue -->
<template>
<div class="card">
<h3>{{ name }}</h3>
<p>{{ email }}</p>
<button @click="handleClick">Contacter</button>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
// Props
const props = defineProps({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
age: {
type: Number,
default: 18
}
})
// Events
const emit = defineEmits(['contact'])
function handleClick() {
emit('contact', { name: props.name, email: props.email })
}
</script>
<template>
<UserCard
name="Nicolas Lema"
email="nicolas@example.com"
:age="25"
@contact="onContact"
/>
</template>
<script setup>
import UserCard from './UserCard.vue'
function onContact(user) {
console.log('Contact:', user)
}
</script>
<script setup>
import { onMounted, onUpdated, onUnmounted } from 'vue'
// Avant montage (équivalent created)
console.log('Setup s\'exécute')
// Après montage DOM
onMounted(() => {
console.log('Composant monté')
// Appels API, initialisation
fetchData()
})
// Après mise à jour
onUpdated(() => {
console.log('Composant mis à jour')
})
// Avant démontage
onUnmounted(() => {
console.log('Composant démonté')
// Nettoyage: event listeners, timers...
})
</script>
<script setup>
import { ref, watch, watchEffect } from 'vue'
const searchQuery = ref('')
const user = ref({ name: 'Nicolas', age: 25 })
// Watch simple
watch(searchQuery, (newVal, oldVal) => {
console.log(`Query changed from ${oldVal} to ${newVal}`)
performSearch(newVal)
})
// Watch avec options
watch(searchQuery, (newVal) => {
performSearch(newVal)
}, {
immediate: true, // Exécuter immédiatement
deep: true // Watch profond (objets/arrays)
})
// Watch objet (deep)
watch(user, (newUser) => {
console.log('User changed:', newUser)
}, { deep: true })
// watchEffect: exécute immédiatement et re-exécute si dépendances changent
watchEffect(() => {
console.log('Search:', searchQuery.value)
// Se ré-exécute automatiquement si searchQuery change
})
</script>
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
import UserProfile from '@/views/UserProfile.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
},
{
path: '/user/:id',
name: 'UserProfile',
component: UserProfile,
props: true // Passer params comme props
},
{
path: '/admin',
name: 'Admin',
component: () => import('@/views/Admin.vue'), // Lazy loading
meta: { requiresAuth: true }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
// Navigation guard
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
next('/login')
} else {
next()
}
})
export default router
<template>
<nav>
<!-- Liens navigation -->
<router-link to="/">Accueil</router-link>
<router-link :to="{ name: 'About' }">À propos</router-link>
<router-link :to="`/user/${userId}`">Profil</router-link>
</nav>
<!-- Vue chargée ici -->
<router-view />
</template>
<script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
// Navigation programmatique
function goToUser(id) {
router.push(`/user/${id}`)
// OU
router.push({ name: 'UserProfile', params: { id } })
}
// Accéder aux params
const userId = route.params.id
// Accéder aux query params
const page = route.query.page
</script>
// stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
// State
const user = ref(null)
const token = ref(localStorage.getItem('token'))
// Getters
const isAuthenticated = computed(() => !!token.value)
const userName = computed(() => user.value?.name)
// Actions
async function login(credentials) {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
})
const data = await response.json()
user.value = data.user
token.value = data.token
localStorage.setItem('token', data.token)
}
function logout() {
user.value = null
token.value = null
localStorage.removeItem('token')
}
return {
user,
token,
isAuthenticated,
userName,
login,
logout
}
})
<template>
<div v-if="userStore.isAuthenticated">
<p>Bonjour {{ userStore.userName }}</p>
<button @click="userStore.logout">Déconnexion</button>
</div>
<div v-else>
<button @click="handleLogin">Connexion</button>
</div>
</template>
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
async function handleLogin() {
await userStore.login({
email: 'user@example.com',
password: 'password'
})
}
</script>
<template>
<div v-if="loading">Chargement...</div>
<div v-else-if="error">Erreur: {{ error }}</div>
<div v-else>
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const users = ref([])
const loading = ref(false)
const error = ref(null)
async function fetchUsers() {
loading.value = true
error.value = null
try {
const response = await fetch('/api/users')
if (!response.ok) {
throw new Error('Erreur réseau')
}
users.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
onMounted(() => {
fetchUsers()
})
</script>
// composables/useFetch.js
import { ref } from 'vue'
export function useFetch(url) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
async function fetch() {
loading.value = true
error.value = null
try {
const response = await fetch(url)
if (!response.ok) throw new Error(response.statusText)
data.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
return { data, loading, error, fetch }
}
// Utilisation
const { data: users, loading, error, fetch } = useFetch('/api/users')
fetch()
Utiliser Composition API (script setup) pour meilleure réutilisabilité
Extraire logique réutilisable dans composables (use*)
Toujours définir types et required pour props
:key unique dans v-for (pas index si modifiable)
Utiliser scoped pour éviter conflits CSS
Routes lazy-loadées pour meilleures performances