Développement Web · Comparative
React vs Angular
vs Vue
Trois frameworks UI comparés techniquement — philosophie, syntaxe, état, routing, performance, tests et guide de choix selon le projet et le contexte.
01 — Vue d'ensemble
Présentation
| Aspect | ⚛ React | 🅰 Angular | V Vue 3 |
|---|---|---|---|
| Type | Bibliothèque UI | Framework complet (opinionated) | Framework progressif |
| Créé par | Meta — 2013 | Google — 2016 | Evan You — 2014 (v3 : 2020) |
| Langage | JS ou TypeScript | TypeScript obligatoire | JS ou TypeScript |
| Syntaxe template | JSX — JS + XML dans le code | HTML + directives Angular | SFC — template HTML séparé |
| Réactivité | Manuelle — useState, setState | Zone.js + Change Detection (ou Signals 16+) | Automatique — ref/reactive/computed |
| Périmètre | UI seulement — choisir le reste | Tout intégré : router, HTTP, forms, DI | UI + écosystème officiel léger |
| Version actuelle | React 19 (2024) | Angular 19 (2024) | Vue 3.5 (2024) |
| npm weekly DL | ~25 millions | ~3,5 millions | ~5 millions |
| Build recommandé | Vite + @vitejs/plugin-react | Angular CLI (esbuild intégré) | Vite + @vitejs/plugin-vue |
02
Philosophie & positionnement
Ce que chaque framework priorise
⚛ React — "JavaScript d'abord"
→ L'UI = du JavaScript (JSX)
→ Liberté totale d'organisation
→ Paradigme fonctionnel (composants = fonctions)
→ Pas de magie cachée — tout est explicite
→ Écosystème énorme (mais choix à faire)
🅰 Angular — "Structure d'abord"
→ Framework d'entreprise complet
→ Convention over configuration
→ TypeScript + DI + RxJS = stack homogène
→ CLI génère une architecture cohérente
→ Idéal grandes équipes — moins d'ambiguïté
→ Courbe d'apprentissage élevée, puis très productif
V Vue — "Progressif et accessible"
→ Templates HTML intuitifs (pas de JSX)
→ Réactivité automatique (pas de setState)
→ Single File Component = tout en un fichier
→ Facile à démarrer, puissant en profondeur
→ Synthèse du meilleur de React et Angular
Analogie pédagogique
React = Lego Technic
Pièces de base + liberté totale
Plus de choix → plus de responsabilité
Peut mener à n'importe quelle architecture
Angular = Kit IKEA complet
Instructions précises, résultat prévisible
Toutes les pièces incluses
Montage plus long, résultat solide
Idéal pour grandes équipes / projets longs
Vue = Lego Creator
Guidé sans être rigide
Bon compromis liberté / structure
La courbe d'apprentissage la plus douce
Marché du travail (France / Belgique) :
React → dominant (startups, agences, scale-ups)
Angular → solide (banques, assurances, grands groupes)
Vue → présent (agences web, PME, contexte asiatique)
03
Scores par critère
■ React ■ Angular ■ Vue
Facilité d'apprentissage
Réactivité automatique
Offres d'emploi (FR/BE)
| Critère | ⚛ | 🅰 | V |
|---|---|---|---|
| Prise en main | 3 | 1 | 5 |
| TypeScript natif | 3 | 5 | 4 |
| Écosystème / libs | 5 | 4 | 3 |
| Cohésion grande équipe | 3 | 5 | 3 |
| Réactivité automatique | 2 | 3 | 5 |
| Lisibilité template | 3 | 4 | 5 |
| Emplois FR/BE | 5 | 4 | 3 |
| Performance runtime | 5 | 4 | 5 |
| SSR intégré | 4 | 4 | 5 |
04 — Comparaisons techniques
Syntaxe côte à côte
Même composant "carte produit" dans les trois frameworks :
⚛ React (TSX)
interface Props {
titre: string
prix: number
onBuy: () => void
}
function ProductCard(
{ titre, prix, onBuy }: Props
) {
const [qty, setQty] =
useState(1)
return (
<div className="card">
<h2>{titre}</h2>
<p>{prix * qty} €</p>
<input type="number"
value={qty}
onChange={e =>
setQty(+e.target.value)}
/>
<button onClick={onBuy}>
Acheter
</button>
</div>
)
}🅰 Angular
@Component({
selector: 'app-product-card',
standalone: true,
imports: [FormsModule],
template: `
<div class="card">
<h2>{{ titre }}</h2>
<p>{{ prix * qty }} €</p>
<input type="number"
[(ngModel)]="qty" />
<button (click)="buy.emit()">
Acheter
</button>
</div>
`
})
export class ProductCardComponent {
@Input() titre!: string
@Input() prix!: number
@Output() buy =
new EventEmitter<void>()
qty = 1
}V Vue 3 (SFC)
<template>
<div class="card">
<h2>{{ titre }}</h2>
<p>{{ prix * qty }} €</p>
<input
type="number"
v-model.number="qty"
/>
<button
@click="emit('buy')">
Acheter
</button>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
titre: string
prix: number
}>()
const emit = defineEmits<{
buy: []
}>()
const qty = ref(1)
</script>05
Gestion de l'état
⚛ React
// Local
const [count, setCount] = useState(0)
setCount(c => c + 1)
// Calculé (mémoïsé)
const doubled = useMemo(
() => count * 2, [count]
)
// Partagé
const ctx = useContext(AppCtx)
// Global — Zustand
const { items, add } = useStore()
// Serveur — TanStack Query
const { data } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
})🅰 Angular
// Local — signal (ng 16+)
const count = signal(0)
count.update(c => c + 1)
// Calculé
const doubled = computed(
() => count() * 2
)
// Service singleton partagé
@Injectable({ providedIn: 'root' })
class AppState {
count = signal(0)
items = signal<Item[]>([])
}
// inject(AppState).count()
// HTTP + async pipe (Observable)
users$ = this.http.get('/api/users')
// {{ users$ | async }}V Vue 3
// Local — réactif automatique
const count = ref(0)
count.value++
// {{ count }} dans le template
// Calculé (auto-mémoïsé)
const doubled = computed(
() => count.value * 2
)
// Partagé — provide/inject
provide('theme', theme)
const theme = inject('theme')
// Global — Pinia (officiel)
const cart = useCartStore()
const { items } = storeToRefs(cart)
cart.addItem(product)| Besoin | ⚛ React | 🅰 Angular | V Vue 3 |
|---|---|---|---|
| État local simple | useState() | signal() ou property | ref() / reactive() |
| Valeur calculée | useMemo(fn, deps) — deps explicites | computed(fn) | computed(fn) — deps auto |
| Effet de bord | useEffect(fn, deps) — explicite | effect() ou ngOnInit() | watch() ou watchEffect() |
| Partage sous-arbre | useContext() | Service scoped ou provide: | provide/inject |
| État global | Zustand / Jotai / Redux Toolkit | NgRx ou Service + Signals | Pinia (officiel, simple) |
| État serveur / cache | TanStack Query (recommandé) | HttpClient + RxJS + async pipe | TanStack Query Vue ou composable |
06
Routing
⚛ React Router v6
// npm install react-router-dom
const router = createBrowserRouter([
{ path: '/',
element: <Home/> },
{ path: '/u/:id',
element: <UserDetail/> },
{ path: '/admin',
element: <Layout/>,
children: [
{ index: true,
element: <Dashboard/> }
] },
])
// Hooks
const navigate = useNavigate()
const { id } = useParams()
navigate('/home')
// Lazy
const Admin = lazy(
() => import('./Admin')
)🅰 Angular Router
// Intégré — app.routes.ts
const routes: Routes = [
{ path: '',
component: HomeComponent },
{ path: 'u/:id',
component: UserDetail,
resolve: { user: userResolver } },
{ path: 'admin',
loadChildren: () =>
import('./admin.routes')
.then(m => m.routes),
canActivate: [authGuard] },
]
// Dans le composant
router = inject(Router)
route = inject(ActivatedRoute)
this.router.navigate(['/home'])
this.route.params.pipe(
map(p => p['id']))V Vue Router
// npm install vue-router
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/',
component: HomePage },
{ path: '/u/:id',
component: UserDetail },
{ path: '/admin',
component: () =>
import('./Admin.vue'),
meta: { auth: true } },
]
})
// Dans le composant
const router = useRouter()
const route = useRoute()
router.push('/home')
route.params.id07
Formulaires
⚛ React Hook Form
// npm install react-hook-form
const {
register,
handleSubmit,
formState: { errors }
} = useForm()
// JSX
<form
onSubmit={handleSubmit(onSubmit)}>
<input
{...register('email', {
required: 'Requis',
pattern: /^[^@]+@[^@]+$/
})}
/>
{errors.email &&
<p>{errors.email.message}</p>}
<button type="submit">OK</button>
</form>🅰 Angular Reactive Forms
// Intégré — aucune lib requise
form = this.fb.group({
email: ['', [
Validators.required,
Validators.email
]],
nom: ['', Validators.required],
})
onSubmit() {
if (this.form.invalid) return
console.log(this.form.value)
}
<!-- template HTML -->
<form [formGroup]="form"
(ngSubmit)="onSubmit()">
<input formControlName="email"/>
@if (form.get('email')?.invalid
&& form.get('email')?.touched) {
<p>Email invalide</p>
}
<button type="submit">OK</button>
</form>V Vue + v-model
// Simple — v-model + ref
const form = reactive({ email: '' })
const errors = reactive({ email: '' })
function validate() {
if (!form.email.includes('@')) {
errors.email = 'Email invalide'
return false
}
return true
}
function submit() {
if (!validate()) return
// soumettre
}
<!-- template :
<form @submit.prevent="submit">
<input v-model="form.email" />
<p v-if="errors.email">
{{ errors.email }}
</p>
<button type="submit">OK</button>
</form>
Avancé → VeeValidate + Zod -->08
Requêtes HTTP
| Aspect | ⚛ React | 🅰 Angular | V Vue 3 |
|---|---|---|---|
| Solution native | Aucune — fetch ou axios | HttpClient intégré + RxJS | Aucune — fetch ou axios |
| Recommandation | TanStack Query (cache, loading, error auto) | HttpClient + async pipe | Composable useFetch ou TanStack Query Vue |
| Intercepteurs | Axios interceptors | HttpInterceptorFn intégré | Axios interceptors |
| Cache automatique | TanStack Query — staleTime, invalidation | Manuel via RxJS operators | TanStack Query Vue ou manuel |
| Annulation requête | AbortController dans useEffect cleanup | takeUntilDestroyed() | AbortController dans onUnmounted |
| État loading/error | TanStack Query — isLoading, isError | RxJS pipeline + async pipe | ref loading/error dans composable |
09
Performance & bundle
| Aspect | ⚛ React | 🅰 Angular | V Vue 3 |
|---|---|---|---|
| Bundle minimal (gzip) | ~45kb (react + react-dom) | ~90-120kb (framework complet) | ~35kb (le plus léger) |
| Algorithme de rendu | Virtual DOM + reconciliation Fiber | Incremental DOM Ivy + Change Detection | Virtual DOM + compilateur statique avancé |
| Optimisation re-renders | Manuelle — React.memo, useMemo, useCallback | Automatique avec Signals / stratégie OnPush | Automatique — réactivité fine granulaire |
| Code splitting | React.lazy() + dynamic import | Lazy routes + @defer intégré | defineAsyncComponent + lazy routes |
| SSR | Next.js / Remix | Angular Universal intégré | Nuxt 3 (premier choix SSR Vue) |
| Benchmark JS | Top 5 — très rapide | Top 10 — rapide | Top 5 — proche React |
10
Tests
| Type | ⚛ React | 🅰 Angular | V Vue 3 |
|---|---|---|---|
| Tests unitaires | Vitest ou Jest | Jasmine + Karma (CLI) ou Jest | Vitest (intégré via create-vue) |
| Tests composants | @testing-library/react | TestBed + ComponentFixture | Vue Test Utils ou @testing-library/vue |
| Tests E2E | Playwright ou Cypress | Playwright ou Cypress | Playwright ou Cypress |
| Mocking | vi.mock() / jest.mock() | DI — remplacer le service dans TestBed | vi.mock() avec Vitest |
| Boilerplate de test | Moyen — configurer jsdom | Élevé — TestBed + imports NgModule | Faible — Vitest + vite.config existant |
| Couverture | vitest --coverage | ng test --code-coverage | vitest run --coverage |
11 — Choisir
Arbre de décision
🎓 Contexte d'apprentissage ?
Je débute en frameworks JS Vue 3 — courbe la plus douce
J'ai déjà du JS/TS → continuer ↓
👥 Taille et durée du projet ?
Solo / petite équipe / prototype React ou Vue
Grande équipe (5+), projet long terme Angular
🏢 Secteur et type de projet ?
Startup, B2C, app produit React
Banque, ERP, portail d'entreprise Angular
Agence web, site + app légère Vue
Full-stack SSR Next.js ou Nuxt
📋 Préférence de syntaxe ?
Tout en JavaScript (JSX) React
HTML séparé + TypeScript strict Angular
HTML lisible + JS minimal Vue
💼 Objectif carrière ?
Maximiser les offres d'emploi React en premier
Viser les grandes entreprises FR Angular
Concepts transposables, démarrage rapide Vue puis React
12
Cas d'usage concrets
| Projet | ⚛ React | 🅰 Angular | V Vue 3 |
|---|---|---|---|
| Dashboard analytics | ✅✅ Recharts + TanStack Query | ✅✅ NgRx + charts | ✅ Charts + Pinia |
| E-commerce | ✅✅ Next.js + SSR | ✅ Angular Universal | ✅✅ Nuxt 3 SSR/SSG |
| Application mobile | ✅✅ React Native | ⚠️ Ionic Angular | ⚠️ Ionic Vue (moins mature) |
| ERP / back-office | ⚠️ Possible mais libre | ✅✅ Cas d'usage principal | ⚠️ Possible avec conventions |
| Site vitrine + CMS | ✅ Next.js + Strapi | ❌ Overkill | ✅✅ Nuxt Content / VitePress |
| Prototypage rapide | ✅✅ Vite + React | ⚠️ Setup plus long | ✅✅ Vite + Vue — le plus rapide |
| Lib de composants UI | ✅✅ Storybook courant | ⚠️ Complexe | ✅✅ Element Plus, Vuetify… |
| App avec Three.js / Canvas | ✅ React + Three.js | ⚠️ Lourd | ✅ Vue + TresJS |
13
Correspondances entre frameworks
| Concept | ⚛ React | 🅰 Angular | V Vue 3 |
|---|---|---|---|
| État local | useState() | signal() / property | ref() |
| Valeur calculée | useMemo(fn, deps) | computed(fn) | computed(fn) |
| Effet de bord | useEffect(fn, deps) | ngOnInit() + ngOnDestroy() | watch() / onMounted() |
| Props parent → enfant | Props destructurées | @Input() | defineProps() |
| Événement enfant → parent | Callback en prop | @Output() + EventEmitter | defineEmits() + emit() |
| Contenu projeté | children | <ng-content> | <slot> |
| Référence DOM | useRef() | @ViewChild() | const el = ref() + ref="el" |
| Logique réutilisable | Custom hooks (use*) | Services injectables | Composables (use*) — identique React |
| Partage global | useContext() / Zustand | Service providedIn: 'root' | provide/inject / Pinia |
| Conditionnel | {cond && <X/>} ou ternaire | @if (cond) { } | v-if="cond" |
| Boucle | {items.map(i => <X key={i.id}/>)} | @for (i of items; track i.id) { } | v-for="i in items" :key="i.id" |
Si tu connais React → Vue
useState(0) → ref(0) (.value dans script)
useMemo(fn, deps) → computed(fn) (deps auto)
useEffect(fn, [x]) → watch(x, fn)
useEffect(fn, []) → onMounted(fn)
props → defineProps()
callback prop → defineEmits() + emit()
children → <slot />
useContext() → inject()
Custom hooks → Composables (même concept !)Si tu connais Angular → React / Vue
@Input() → props / defineProps()
@Output() → callback prop / emit()
Service root → Zustand store / Pinia store
ngOnInit() → useEffect(fn,[]) / onMounted()
ngOnDestroy() → useEffect return / onUnmounted()
@if / @for → {cond && <X/>} / v-if, v-for
async pipe → TanStack Query / composable
ng-content → children / <slot>
Reactive Forms → React Hook Form / VeeValidate14 — Référence
Tableau récapitulatif
| Critère | ⚛ React | 🅰 Angular | V Vue 3 |
|---|---|---|---|
| Type | Bibliothèque UI | Framework complet | Framework progressif |
| Courbe d'apprentissage | Modérée (hooks) | Élevée (RxJS, DI, decorators) | Faible — la plus douce |
| Liberté architecturale | Haute | Faible — imposée | Moyenne |
| Réactivité | Manuelle (setState) | Auto (Zone.js) ou Signals | Automatique et fine |
| TypeScript | Optionnel | Obligatoire | Optionnel, très bien supporté |
| Routing | react-router-dom (externe) | @angular/router (intégré) | vue-router (officiel) |
| État global | Zustand / Jotai / Redux | NgRx ou Services + Signals | Pinia (officiel, simple) |
| HTTP | fetch / axios / TanStack Query | HttpClient (intégré) | fetch / axios / composable |
| Emplois FR/BE | 🟢🟢🟢🟢🟢 Dominant | 🟢🟢🟢🟢 Solide entreprise | 🟢🟢🟢 Présent agences/PME |
| À apprendre en premier ? | ✅ Oui — le plus demandé | ⚠️ Après React ou contexte entreprise | ✅ Oui — le plus accessible |
🎯
Recommandation pédagogique : commencer par Vue 3 pour la clarté des templates et la réactivité automatique, puis React pour maîtriser les concepts fondamentaux (état explicite, hooks) et maximiser les opportunités professionnelles. Angular s'aborde ensuite naturellement — son architecture stricte devient un avantage dans les projets d'entreprise.