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.

Présentation

Aspect ⚛ React 🅰 Angular V Vue 3
TypeBibliothèque UIFramework complet (opinionated)Framework progressif
Créé parMeta — 2013Google — 2016Evan You — 2014 (v3 : 2020)
LangageJS ou TypeScriptTypeScript obligatoireJS ou TypeScript
Syntaxe templateJSX — JS + XML dans le codeHTML + directives AngularSFC — template HTML séparé
RéactivitéManuelle — useState, setStateZone.js + Change Detection (ou Signals 16+)Automatique — ref/reactive/computed
PérimètreUI seulement — choisir le resteTout intégré : router, HTTP, forms, DIUI + écosystème officiel léger
Version actuelleReact 19 (2024)Angular 19 (2024)Vue 3.5 (2024)
npm weekly DL~25 millions~3,5 millions~5 millions
Build recommandéVite + @vitejs/plugin-reactAngular CLI (esbuild intégré)Vite + @vitejs/plugin-vue

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)

Scores par critère

React   Angular   Vue

Facilité d'apprentissage

React
3/5
Angular
1.5/5
Vue
4.5/5

Réactivité automatique

React
2/5
Angular
3/5
Vue
5/5

Offres d'emploi (FR/BE)

React
5/5
Angular
4/5
Vue
3/5
Critère 🅰 V
Prise en main315
TypeScript natif354
Écosystème / libs543
Cohésion grande équipe353
Réactivité automatique235
Lisibilité template345
Emplois FR/BE543
Performance runtime545
SSR intégré445

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>

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 simpleuseState()signal() ou propertyref() / reactive()
Valeur calculéeuseMemo(fn, deps) — deps explicitescomputed(fn)computed(fn) — deps auto
Effet de borduseEffect(fn, deps) — expliciteeffect() ou ngOnInit()watch() ou watchEffect()
Partage sous-arbreuseContext()Service scoped ou provide:provide/inject
État globalZustand / Jotai / Redux ToolkitNgRx ou Service + SignalsPinia (officiel, simple)
État serveur / cacheTanStack Query (recommandé)HttpClient + RxJS + async pipeTanStack Query Vue ou composable

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

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

Requêtes HTTP

Aspect ⚛ React 🅰 Angular V Vue 3
Solution nativeAucune — fetch ou axiosHttpClient intégré + RxJSAucune — fetch ou axios
RecommandationTanStack Query (cache, loading, error auto)HttpClient + async pipeComposable useFetch ou TanStack Query Vue
IntercepteursAxios interceptorsHttpInterceptorFn intégréAxios interceptors
Cache automatiqueTanStack Query — staleTime, invalidationManuel via RxJS operatorsTanStack Query Vue ou manuel
Annulation requêteAbortController dans useEffect cleanuptakeUntilDestroyed()AbortController dans onUnmounted
État loading/errorTanStack Query — isLoading, isErrorRxJS pipeline + async piperef loading/error dans composable

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 renduVirtual DOM + reconciliation FiberIncremental DOM Ivy + Change DetectionVirtual DOM + compilateur statique avancé
Optimisation re-rendersManuelle — React.memo, useMemo, useCallbackAutomatique avec Signals / stratégie OnPushAutomatique — réactivité fine granulaire
Code splittingReact.lazy() + dynamic importLazy routes + @defer intégrédefineAsyncComponent + lazy routes
SSRNext.js / RemixAngular Universal intégréNuxt 3 (premier choix SSR Vue)
Benchmark JSTop 5 — très rapideTop 10 — rapideTop 5 — proche React

Tests

Type ⚛ React 🅰 Angular V Vue 3
Tests unitairesVitest ou JestJasmine + Karma (CLI) ou JestVitest (intégré via create-vue)
Tests composants@testing-library/reactTestBed + ComponentFixtureVue Test Utils ou @testing-library/vue
Tests E2EPlaywright ou CypressPlaywright ou CypressPlaywright ou Cypress
Mockingvi.mock() / jest.mock()DI — remplacer le service dans TestBedvi.mock() avec Vitest
Boilerplate de testMoyen — configurer jsdomÉlevé — TestBed + imports NgModuleFaible — Vitest + vite.config existant
Couverturevitest --coverageng test --code-coveragevitest run --coverage

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

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

Correspondances entre frameworks

Concept ⚛ React 🅰 Angular V Vue 3
État localuseState()signal() / propertyref()
Valeur calculéeuseMemo(fn, deps)computed(fn)computed(fn)
Effet de borduseEffect(fn, deps)ngOnInit() + ngOnDestroy()watch() / onMounted()
Props parent → enfantProps destructurées@Input()defineProps()
Événement enfant → parentCallback en prop@Output() + EventEmitterdefineEmits() + emit()
Contenu projetéchildren<ng-content><slot>
Référence DOMuseRef()@ViewChild()const el = ref() + ref="el"
Logique réutilisableCustom hooks (use*)Services injectablesComposables (use*) — identique React
Partage globaluseContext() / ZustandService 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 / VeeValidate

Tableau récapitulatif

Critère ⚛ React 🅰 Angular V Vue 3
TypeBibliothèque UIFramework completFramework progressif
Courbe d'apprentissageModérée (hooks)Élevée (RxJS, DI, decorators)Faible — la plus douce
Liberté architecturaleHauteFaible — imposéeMoyenne
RéactivitéManuelle (setState)Auto (Zone.js) ou SignalsAutomatique et fine
TypeScriptOptionnelObligatoireOptionnel, très bien supporté
Routingreact-router-dom (externe)@angular/router (intégré)vue-router (officiel)
État globalZustand / Jotai / ReduxNgRx ou Services + SignalsPinia (officiel, simple)
HTTPfetch / axios / TanStack QueryHttpClient (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.