Vue.js et TypeScript : Guide Complet pour la Composition API avec Typage Statique

Combinez Vue 3, TypeScript et la Composition API pour un code plus sur. Configuration, typage et bonnes pratiques pour vos projets Vue.

Mahmoud DEVO
Mahmoud DEVO
December 28, 2025 9 min read
Vue.js et TypeScript : Guide Complet pour la Composition API avec Typage Statique

Introduction

Vous etes peut-etre deja familiarise avec la programmation orientee objet dans Vue.js et avez utilise l’API de composition pour construire vos composants. Mais avez-vous deja explore les fonctionnalites de TypeScript dans votre application Vue ? Dans ce guide complet, nous allons presenter les fondements du TypeScript et montrer comment l’utiliser efficacement avec la Composition API de Vue 3.

L’adoption de TypeScript dans l’ecosysteme Vue.js a connu une croissance exponentielle ces dernieres annees. Vue 3 a ete entierement reecrit en TypeScript, ce qui garantit une integration native et une experience developpeur optimale. Cette decision strategique de l’equipe Vue temoigne de l’importance du typage statique dans le developpement d’applications modernes.

Dans cet article, nous allons explorer en profondeur comment TypeScript ameliore votre code Vue, depuis les types de base jusqu’aux patterns avances de typage des composants. Que vous soyez debutant en TypeScript ou developpeur experimente cherchant a optimiser vos pratiques, ce guide vous fournira les connaissances necessaires pour maitriser cette combinaison puissante.

Pourquoi utiliser TypeScript avec Vue.js ?

Le JavaScript est un langage faiblement type, ce qui signifie que vous pouvez assigner n’importe quel type de donnees a une variable sans erreur. Par exemple :

// JavaScript - Pas d'erreur a la compilation
const one = 1;
const two = 'two';

// Ceci peut causer des bugs subtils
function add(a, b) {
  return a + b;
}

add(1, '2'); // Retourne '12' au lieu de 3 !

Cependant, cela peut conduire a des erreurs difficiles a detecter lorsque votre code devient plus complexe. Le TypeScript est un langage superset du JavaScript qui ajoute des fonctionnalites de typage statique pour empecher ces types d’erreurs.

Les avantages concrets de TypeScript

1. Detection des erreurs a la compilation

TypeScript detecte les erreurs avant meme que votre code ne s’execute :

function add(a: number, b: number): number {
  return a + b;
}

add(1, '2'); // Erreur TypeScript : l'argument de type 'string'
             // n'est pas assignable au parametre de type 'number'

2. Autocompletion intelligente

Votre IDE connait exactement les proprietes et methodes disponibles :

interface User {
  id: number;
  name: string;
  email: string;
  createdAt: Date;
}

const user: User = { /* ... */ };
user. // L'IDE suggere : id, name, email, createdAt

3. Refactoring securise

Renommer une propriete ou modifier une signature de fonction devient sur car TypeScript signale toutes les utilisations incorrectes.

4. Documentation integree

Les types servent de documentation vivante pour votre code :

// La signature de la fonction documente son utilisation
function formatUserName(
  firstName: string,
  lastName: string,
  options?: { uppercase?: boolean }
): string {
  const fullName = `${firstName} ${lastName}`;
  return options?.uppercase ? fullName.toUpperCase() : fullName;
}

Qu’est-ce que le TypeScript ?

Le TypeScript a ete cree en 2012 par Microsoft et est desormais maintenu par une large communaute. Il est concu pour :

  • Ameliorer la lisibilite du code grace aux annotations de type
  • Prevenir les erreurs de programmation courantes avant l’execution
  • Reduire le temps et l’effort necessaires pour deboguer des applications
  • Faciliter le travail en equipe grace a des contrats clairs entre modules
  • Permettre une meilleure scalabilite des projets

Le TypeScript n’est pas un langage different, mais une extension du JavaScript qui ajoute des fonctionnalites de typage statique. Les types sont verifies pendant la compilation (statiquement) au lieu de la phase d’execution (dynamique).

Les types de base TypeScript

Avant de plonger dans l’integration avec Vue, maitrisons les types fondamentaux de TypeScript.

Types primitifs

// String - chaines de caracteres
const name: string = 'Mahmoud';
const greeting: string = `Bonjour, ${name}`;

// Number - nombres (entiers et decimaux)
const age: number = 30;
const price: number = 19.99;
const hex: number = 0xf00d;

// Boolean - valeurs booleennes
const isActive: boolean = true;
const hasPermission: boolean = false;

// Null et Undefined
const nullValue: null = null;
const undefinedValue: undefined = undefined;

// Symbol (ES6)
const uniqueKey: symbol = Symbol('key');

// BigInt (grands nombres)
const bigNumber: bigint = 9007199254740991n;

Types complexes : Arrays

// Tableau de nombres - deux syntaxes equivalentes
const numbers: number[] = [1, 2, 3, 4, 5];
const scores: Array<number> = [98, 85, 92];

// Tableau de chaines
const fruits: string[] = ['pomme', 'banane', 'orange'];

// Tableau d'objets
interface Product {
  id: number;
  name: string;
  price: number;
}

const products: Product[] = [
  { id: 1, name: 'Laptop', price: 999 },
  { id: 2, name: 'Mouse', price: 29 }
];

// Tableau mixte avec tuple
const tuple: [string, number, boolean] = ['test', 42, true];

Types complexes : Objects

// Objet simple avec type inline
const user: { name: string; age: number } = {
  name: 'Alice',
  age: 25
};

// Objet avec proprietes optionnelles
const config: {
  host: string;
  port: number;
  ssl?: boolean;  // optionnel
} = {
  host: 'localhost',
  port: 3000
  // ssl est optionnel, pas besoin de le definir
};

// Record pour les objets dynamiques
const userRoles: Record<string, string[]> = {
  admin: ['read', 'write', 'delete'],
  editor: ['read', 'write'],
  viewer: ['read']
};

Interfaces et Types personnalises

Les interfaces et les types personnalises sont essentiels pour definir des structures de donnees complexes dans vos applications Vue.

Interfaces

// Interface de base
interface User {
  id: number;
  username: string;
  email: string;
  createdAt: Date;
}

// Interface avec proprietes optionnelles et readonly
interface UserProfile extends User {
  readonly id: number;        // ne peut pas etre modifie
  avatar?: string;            // optionnel
  bio?: string;
  socialLinks?: {
    twitter?: string;
    github?: string;
    linkedin?: string;
  };
}

// Interface pour les fonctions
interface Validator {
  (value: string): boolean;
}

const emailValidator: Validator = (value) => {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
};

// Interface generique
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
  timestamp: Date;
}

// Utilisation
const userResponse: ApiResponse<User> = {
  data: { id: 1, username: 'john', email: 'john@example.com', createdAt: new Date() },
  status: 200,
  message: 'Success',
  timestamp: new Date()
};

Types avec type alias

// Type alias simple
type ID = number | string;

// Union types
type Status = 'pending' | 'active' | 'completed' | 'cancelled';

// Type conditionnel
type Nullable<T> = T | null;

// Type avec intersection
type Employee = User & {
  department: string;
  salary: number;
  hireDate: Date;
};

// Type pour les evenements Vue
type EventPayload = {
  type: string;
  target: HTMLElement;
  data?: unknown;
};

// Type utilitaire : Partial (toutes les proprietes optionnelles)
type PartialUser = Partial<User>;

// Type utilitaire : Pick (selection de proprietes)
type UserCredentials = Pick<User, 'email' | 'username'>;

// Type utilitaire : Omit (exclusion de proprietes)
type PublicUser = Omit<User, 'email' | 'createdAt'>;

Quand utiliser Interface vs Type ?

CaracteristiqueInterfaceType
Extensionextends& (intersection)
Declaration fusionneeOuiNon
Union typesNonOui
TuplesNonOui
PrimitifsNonOui

Recommandation : Utilisez interface pour les objets et les classes, type pour les unions, les tuples et les types complexes.

Typage de ref() et reactive()

La Composition API de Vue 3 offre deux methodes principales pour creer des donnees reactives : ref() et reactive(). Voyons comment les typer correctement.

Typage de ref()

import { ref, Ref } from 'vue';

// Inference automatique - TypeScript deduit le type
const count = ref(0);           // Ref<number>
const message = ref('Hello');   // Ref<string>
const isVisible = ref(true);    // Ref<boolean>

// Typage explicite - recommande pour les types complexes
const user = ref<User | null>(null);
const items = ref<Product[]>([]);

// Typage avec interface
interface FormData {
  username: string;
  email: string;
  password: string;
  rememberMe: boolean;
}

const formData = ref<FormData>({
  username: '',
  email: '',
  password: '',
  rememberMe: false
});

// Acces a la valeur (TypeScript connait le type)
console.log(formData.value.username); // string
formData.value.email = 'test@example.com'; // OK
formData.value.invalidProp = 'test'; // Erreur TypeScript !

Typage de reactive()

import { reactive } from 'vue';

// Inference automatique
const state = reactive({
  count: 0,
  message: 'Hello'
});

// Typage explicite avec interface
interface AppState {
  user: User | null;
  isAuthenticated: boolean;
  notifications: Notification[];
  theme: 'light' | 'dark';
}

const appState = reactive<AppState>({
  user: null,
  isAuthenticated: false,
  notifications: [],
  theme: 'light'
});

// Acces direct (pas de .value avec reactive)
appState.isAuthenticated = true;
appState.theme = 'dark';
appState.theme = 'invalid'; // Erreur : 'invalid' n'est pas assignable a 'light' | 'dark'

// Pattern recommande : definir l'interface separement
interface CartState {
  items: CartItem[];
  total: number;
  couponCode: string | null;
  isLoading: boolean;
}

interface CartItem {
  productId: number;
  quantity: number;
  price: number;
}

const cart = reactive<CartState>({
  items: [],
  total: 0,
  couponCode: null,
  isLoading: false
});

Difference entre ref et reactive

// ref : pour les valeurs primitives et les references
const count = ref(0);
count.value++; // Acces via .value

// reactive : pour les objets complexes
const state = reactive({ count: 0 });
state.count++; // Acces direct

// Attention : reactive perd la reactivite si destructure
const { count } = reactive({ count: 0 }); // count n'est plus reactif !

// Solution : utiliser toRefs
import { toRefs } from 'vue';
const state = reactive({ count: 0, name: 'test' });
const { count, name } = toRefs(state); // Maintenant reactif !

Typage des computed et watch

Les proprietes calculees et les observateurs sont des elements cles de la reactivite Vue. Voici comment les typer correctement.

Typage de computed()

import { ref, computed, ComputedRef } from 'vue';

// Computed simple - inference automatique
const firstName = ref('John');
const lastName = ref('Doe');

const fullName = computed(() => `${firstName.value} ${lastName.value}`);
// Type infere : ComputedRef<string>

// Computed avec typage explicite
const items = ref<Product[]>([]);

const totalPrice: ComputedRef<number> = computed(() => {
  return items.value.reduce((sum, item) => sum + item.price, 0);
});

// Computed avec getter et setter
const selectedId = ref<number | null>(null);
const users = ref<User[]>([]);

const selectedUser = computed<User | undefined>({
  get: () => users.value.find(u => u.id === selectedId.value),
  set: (user) => {
    selectedId.value = user?.id ?? null;
  }
});

// Computed avec type complexe
interface FilteredResult {
  items: Product[];
  count: number;
  hasMore: boolean;
}

const searchQuery = ref('');
const filteredProducts = computed<FilteredResult>(() => {
  const filtered = items.value.filter(item =>
    item.name.toLowerCase().includes(searchQuery.value.toLowerCase())
  );
  return {
    items: filtered.slice(0, 10),
    count: filtered.length,
    hasMore: filtered.length > 10
  };
});

Typage de watch()

import { ref, watch, WatchStopHandle } from 'vue';

// Watch simple
const searchQuery = ref('');

watch(searchQuery, (newValue, oldValue) => {
  // newValue et oldValue sont types comme string
  console.log(`Query changed from "${oldValue}" to "${newValue}"`);
});

// Watch avec options
const userId = ref<number | null>(null);

watch(
  userId,
  async (newId, oldId) => {
    if (newId !== null) {
      // Logique de chargement
      await loadUserData(newId);
    }
  },
  {
    immediate: true,  // Execute immediatement
    deep: false       // Pas de surveillance profonde
  }
);

// Watch multiple sources
const page = ref(1);
const perPage = ref(10);

watch(
  [page, perPage],
  ([newPage, newPerPage], [oldPage, oldPerPage]) => {
    // Types : [number, number]
    console.log(`Page: ${oldPage} -> ${newPage}`);
    console.log(`Per page: ${oldPerPage} -> ${newPerPage}`);
  }
);

// Watch avec objet reactif
interface Filters {
  category: string;
  minPrice: number;
  maxPrice: number;
  inStock: boolean;
}

const filters = reactive<Filters>({
  category: 'all',
  minPrice: 0,
  maxPrice: 1000,
  inStock: true
});

watch(
  () => ({ ...filters }), // Copie pour detecter les changements
  (newFilters, oldFilters) => {
    // newFilters et oldFilters sont types comme Filters
    applyFilters(newFilters);
  },
  { deep: true }
);

// Stopper un watcher
const stopWatch: WatchStopHandle = watch(searchQuery, () => {
  // ...
});

// Plus tard, arreter la surveillance
stopWatch();

watchEffect()

import { ref, watchEffect, WatchStopHandle } from 'vue';

const userId = ref<number | null>(null);
const userData = ref<User | null>(null);

// watchEffect detecte automatiquement les dependances
const stop: WatchStopHandle = watchEffect(async (onCleanup) => {
  if (userId.value === null) {
    userData.value = null;
    return;
  }

  const controller = new AbortController();

  onCleanup(() => {
    controller.abort(); // Annuler la requete si le watcher est relance
  });

  try {
    const response = await fetch(`/api/users/${userId.value}`, {
      signal: controller.signal
    });
    userData.value = await response.json();
  } catch (error) {
    if (error instanceof Error && error.name !== 'AbortError') {
      console.error('Failed to fetch user:', error);
    }
  }
});

Typage des Props et Emits

Le typage des props et des emits est crucial pour creer des composants robustes et reutilisables.

defineProps() avec typage

<script setup lang="ts">
// Methode 1 : Typage avec generique (recommande)
interface Props {
  title: string;
  count?: number;
  items: string[];
  user: User | null;
  variant?: 'primary' | 'secondary' | 'danger';
}

const props = defineProps<Props>();

// Acces aux props
console.log(props.title);        // string
console.log(props.count);        // number | undefined
console.log(props.items);        // string[]
console.log(props.variant);      // 'primary' | 'secondary' | 'danger' | undefined

// Methode 2 : Avec valeurs par defaut (withDefaults)
interface PropsWithDefaults {
  title: string;
  count?: number;
  variant?: 'primary' | 'secondary' | 'danger';
  disabled?: boolean;
}

const props = withDefaults(defineProps<PropsWithDefaults>(), {
  count: 0,
  variant: 'primary',
  disabled: false
});

// Maintenant props.count est number (pas number | undefined)
console.log(props.count); // number
</script>

defineEmits() avec typage

<script setup lang="ts">
// Methode 1 : Typage avec interface
interface Emits {
  (event: 'update', value: string): void;
  (event: 'submit', data: FormData): void;
  (event: 'close'): void;
  (event: 'error', message: string, code: number): void;
}

const emit = defineEmits<Emits>();

// Utilisation
emit('update', 'nouvelle valeur');       // OK
emit('submit', formData);                // OK
emit('close');                           // OK
emit('error', 'Something went wrong', 500); // OK
emit('update', 123);                     // Erreur : 123 n'est pas une string

// Methode 2 : Syntaxe alternative (Vue 3.3+)
const emit = defineEmits<{
  update: [value: string];
  submit: [data: FormData];
  close: [];
  error: [message: string, code: number];
}>();

// Methode 3 : Avec validation runtime
const emit = defineEmits({
  update: (value: string) => {
    return value.length > 0;
  },
  submit: (data: FormData) => {
    return data !== null;
  }
});
</script>

Exemple complet de composant type

<script setup lang="ts">
import { ref, computed, watch } from 'vue';

// Types
interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'guest';
}

interface Props {
  users: User[];
  selectedId?: number | null;
  searchable?: boolean;
  maxDisplay?: number;
}

interface Emits {
  (event: 'select', user: User): void;
  (event: 'delete', userId: number): void;
  (event: 'search', query: string): void;
}

// Props avec defaults
const props = withDefaults(defineProps<Props>(), {
  selectedId: null,
  searchable: true,
  maxDisplay: 10
});

// Emits
const emit = defineEmits<Emits>();

// State local
const searchQuery = ref('');
const isDropdownOpen = ref(false);

// Computed
const filteredUsers = computed(() => {
  let result = props.users;

  if (searchQuery.value) {
    const query = searchQuery.value.toLowerCase();
    result = result.filter(user =>
      user.name.toLowerCase().includes(query) ||
      user.email.toLowerCase().includes(query)
    );
  }

  return result.slice(0, props.maxDisplay);
});

const selectedUser = computed(() =>
  props.users.find(u => u.id === props.selectedId)
);

// Watchers
watch(searchQuery, (newQuery) => {
  emit('search', newQuery);
});

// Methods
function handleSelect(user: User): void {
  emit('select', user);
  isDropdownOpen.value = false;
}

function handleDelete(userId: number): void {
  if (confirm('Etes-vous sur de vouloir supprimer cet utilisateur ?')) {
    emit('delete', userId);
  }
}
</script>

<template>
  <div class="user-selector">
    <input
      v-if="searchable"
      v-model="searchQuery"
      type="text"
      placeholder="Rechercher..."
    />

    <ul v-if="filteredUsers.length">
      <li
        v-for="user in filteredUsers"
        :key="user.id"
        :class="{ selected: user.id === selectedId }"
        @click="handleSelect(user)"
      >
        {{ user.name }} ({{ user.email }})
        <button @click.stop="handleDelete(user.id)">Supprimer</button>
      </li>
    </ul>

    <p v-else>Aucun utilisateur trouve</p>
  </div>
</template>

Tableau comparatif JavaScript vs TypeScript

AspectJavaScriptTypeScript
TypageDynamique (runtime)Statique (compilation)
Detection erreursA l’executionA la compilation
AutocompletionLimiteeComplete et precise
RefactoringRisqueSecurise
DocumentationCommentaires manuelsTypes = documentation
Courbe d’apprentissageFacileModeree
ConfigurationAucunetsconfig.json
CompatibiliteUniverselleNecessite compilation
ProductiviteRapide au debutRapide a long terme
MaintenanceDifficile sur gros projetsFacilitee
Taille equipePetite equipe OKIdeal grandes equipes
Support IDEBasiqueExcellent

Configuration du projet Vue + TypeScript

Creation d’un nouveau projet

Pour creer un projet Vue avec TypeScript, vous pouvez utiliser l’outil CLI de Vue. Lorsque vous creez un nouveau projet avec npm create vue@latest, choisissez l’option TypeScript pour activer la compilation TypeScript.

npm create vue@latest my-project

# Selectionnez "Yes" pour TypeScript
# Selectionnez "Yes" pour Vue Router (si necessaire)
# Selectionnez "Yes" pour Pinia (si necessaire)

Configuration tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

Editeur de code

Nous recommandons d’utiliser Visual Studio Code pour developper votre application TypeScript. Cet editeur fournit une configuration out-of-the-box pour le TypeScript et vous permettra d’utiliser des fonctionnalites comme la prise en charge de l’inference TypeScript.

Installez egalement les extensions suivantes :

  • Volar : Extension officielle pour Vue 3 avec support TypeScript complet
  • TypeScript Vue Plugin : Integration avancee TypeScript/Vue
  • ESLint : Detection des erreurs et warnings dans votre editeur
  • Prettier : Formatage automatique du code

Bonnes Pratiques TypeScript avec Vue

1. Toujours definir les interfaces pour les props

// Mauvais - Types inline
const props = defineProps<{
  title: string;
  items: { id: number; name: string }[];
}>();

// Bon - Interface separee et reutilisable
interface Item {
  id: number;
  name: string;
}

interface Props {
  title: string;
  items: Item[];
}

const props = defineProps<Props>();

2. Utiliser des types stricts pour les etats

// Mauvais - Type trop generique
const status = ref<string>('loading');

// Bon - Union type restrictif
type LoadingStatus = 'idle' | 'loading' | 'success' | 'error';
const status = ref<LoadingStatus>('idle');

3. Typer explicitement les refs nullables

// Mauvais - Le type initial peut etre trompeur
const user = ref(null); // Ref<null> - probleme !

// Bon - Type explicite
const user = ref<User | null>(null); // Ref<User | null>

4. Creer des types utilitaires reutilisables

// types/api.ts
export interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

export interface PaginatedResponse<T> extends ApiResponse<T[]> {
  meta: {
    currentPage: number;
    totalPages: number;
    totalItems: number;
    perPage: number;
  };
}

export type AsyncState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: string };

5. Utiliser les generiques pour les composables

// composables/useFetch.ts
export function useFetch<T>(url: string) {
  const data = ref<T | null>(null);
  const error = ref<string | null>(null);
  const isLoading = ref(false);

  async function execute(): Promise<void> {
    isLoading.value = true;
    error.value = null;

    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error('Network error');
      data.value = await response.json();
    } catch (e) {
      error.value = e instanceof Error ? e.message : 'Unknown error';
    } finally {
      isLoading.value = false;
    }
  }

  return { data, error, isLoading, execute };
}

// Utilisation
const { data, isLoading } = useFetch<User[]>('/api/users');

6. Organiser les types dans des fichiers dedies

src/
├── types/
│   ├── index.ts      # Re-export tous les types
│   ├── user.ts       # Types lies aux utilisateurs
│   ├── product.ts    # Types lies aux produits
│   ├── api.ts        # Types pour les reponses API
│   └── forms.ts      # Types pour les formulaires
├── components/
├── composables/
└── ...

Pieges Courants a Eviter

1. Oublier le typage des refs dans les templates

// Piege : ref.value n'est pas automatiquement type dans les event handlers
const inputRef = ref<HTMLInputElement | null>(null);

// Mauvais - inputRef.value pourrait etre null
function focusInput() {
  inputRef.value.focus(); // Erreur potentielle !
}

// Bon - Verification de nullite
function focusInput() {
  inputRef.value?.focus();
  // ou
  if (inputRef.value) {
    inputRef.value.focus();
  }
}

2. Confusion entre ref et reactive avec le destructuring

// Piege : Perte de reactivite
const state = reactive({ count: 0, name: 'test' });
const { count } = state; // count n'est plus reactif !
count++; // Ne declenche pas de mise a jour

// Solution : Utiliser toRefs
import { toRefs } from 'vue';
const { count, name } = toRefs(state); // Maintenant reactif
count.value++; // Fonctionne correctement

3. Type any trop permissif

// Mauvais - Desactive la verification de type
const data: any = fetchData();
data.whatever.you.want; // Pas d'erreur mais dangereux !

// Bon - Type unknown plus sur
const data: unknown = fetchData();
if (isUser(data)) { // Type guard
  console.log(data.name); // Maintenant type comme User
}

4. Ignorer les types de retour des fonctions async

// Mauvais - Type de retour implicite
async function fetchUser(id: number) {
  const response = await fetch(`/api/users/${id}`);
  return response.json(); // Type: Promise<any>
}

// Bon - Type de retour explicite
async function fetchUser(id: number): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  return response.json() as User;
}

5. Mauvaise utilisation des assertions de type

// Mauvais - Assertion sans verification
const user = data as User; // Dangereux si data n'est pas un User

// Bon - Utiliser un type guard
function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'id' in obj &&
    'name' in obj &&
    'email' in obj
  );
}

if (isUser(data)) {
  // data est maintenant type comme User de maniere sure
  console.log(data.email);
}

Conclusion

Dans ce guide complet, nous avons explore en profondeur l’integration de TypeScript avec Vue.js et la Composition API. Voici un recapitulatif des points cles a retenir :

Les fondamentaux :

  • TypeScript ajoute un typage statique au JavaScript, detectant les erreurs avant l’execution
  • Vue 3 est nativement ecrit en TypeScript, garantissant une integration optimale
  • Les types de base (string, number, boolean, array, object) forment la fondation du typage

Le typage Vue specifique :

  • ref<T>() pour les valeurs primitives avec acces via .value
  • reactive<T>() pour les objets complexes avec acces direct
  • computed<T>() avec inference automatique ou typage explicite
  • watch() et watchEffect() pour la surveillance reactive typee
  • defineProps<T>() et defineEmits<T>() pour les composants type-safe

Les bonnes pratiques :

  • Definir des interfaces reutilisables pour les structures de donnees
  • Utiliser des unions types restrictives plutot que des strings generiques
  • Creer des composables generiques pour la reutilisabilite
  • Organiser les types dans des fichiers dedies
  • Toujours verifier les valeurs nullables avant utilisation

Les pieges a eviter :

  • Ne pas oublier la perte de reactivite lors du destructuring
  • Eviter le type any au profit de unknown avec type guards
  • Toujours typer explicitement les retours de fonctions async
  • Preferer les type guards aux assertions de type non securisees

TypeScript avec Vue 3 represente une combinaison puissante pour developper des applications robustes et maintenables. L’investissement initial dans l’apprentissage du typage est largement compense par la reduction des bugs, l’amelioration de la productivite et la facilite de maintenance a long terme.

Nous vous recommandons de commencer par activer le mode strict dans votre configuration TypeScript et d’adopter progressivement les bonnes pratiques presentees dans ce guide. Avec le temps, le typage deviendra naturel et vous ne pourrez plus vous en passer !

N’hesitez pas a partager vos questions ou vos commentaires. Bon developpement avec Vue.js et TypeScript !

Advertisement

In-Article Ad

Dev Mode

Share this article

Mahmoud DEVO

Mahmoud DEVO

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles