Typage des props Vue.js avec TypeScript et PropType pour un code robuste

Maitrisez le typage explicite des props dans Vue 3 avec TypeScript. Utilisez PropType et les interfaces pour un code type-safe.

Mahmoud DEVO
Mahmoud DEVO
December 28, 2025 9 min read
Typage des props Vue.js avec TypeScript et PropType pour un code robuste

Typage des props Vue.js avec TypeScript et PropType

Introduction

Le typage des props dans Vue.js avec TypeScript represente l’une des fondations les plus importantes pour construire des applications robustes. Les props constituent le mecanisme principal de communication entre composants. Sans un typage adequat, vous vous exposez a des erreurs runtime difficiles a debugger.

TypeScript detecte les erreurs avant meme l’execution du code, ce qui reduit considerablement le temps de debug. Dans cet article, nous explorons les techniques de typage des props dans Vue 3.

Pourquoi le typage des props est essentiel

Detection precoce des erreurs

// Sans typage - erreurs decouvertes a l'execution
props: {
  user: Object,
  count: Number
}

Avec ce code, rien n’empeche de passer un objet avec une structure incorrecte.

Autocompletion et IntelliSense

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

// Votre IDE proposera id, name, role
props.user.name // Autocompletion disponible

Documentation vivante

Les types servent de documentation qui ne peut pas devenir obsolete. Contrairement aux commentaires, les types sont verifies par le compilateur.

defineProps vs props avec PropType

Options API avec PropType

import { defineComponent, PropType } from 'vue';

interface Product {
  id: number;
  name: string;
  price: number;
}

export default defineComponent({
  props: {
    product: {
      type: Object as PropType<Product>,
      required: true
    },
    quantity: {
      type: Number,
      default: 1
    },
    tags: {
      type: Array as PropType<string[]>,
      default: () => []
    }
  },
  setup(props) {
    console.log(props.product.name);
    return {};
  }
});

Composition API avec defineProps generique

<script setup lang="ts">
interface Product {
  id: number;
  name: string;
  price: number;
}

interface Props {
  product: Product;
  quantity?: number;
  tags?: string[];
}

const props = defineProps<Props>();
</script>

Comparaison des deux approches

AspectPropType (Options API)defineProps generique
SyntaxePlus verbosePlus concise
Validation runtimeOuiNon par defaut
Valeurs par defautDans l’objet propsAvec withDefaults
Inference de typeBonneExcellente

Props required vs optional

Props obligatoires avec PropType

export default defineComponent({
  props: {
    user: {
      type: Object as PropType<User>,
      required: true
    },
    status: {
      type: String as PropType<'active' | 'inactive'>,
      required: true,
      validator: (value: string) => ['active', 'inactive'].includes(value)
    }
  }
});

Props obligatoires avec defineProps

<script setup lang="ts">
interface Props {
  user: User;                         // Obligatoire
  status: 'active' | 'inactive';      // Obligatoire
  title?: string;                     // Optionnelle
}

const props = defineProps<Props>();
</script>

Props optionnelles avec valeurs par defaut

<script setup lang="ts">
interface Props {
  config?: { theme: 'light' | 'dark' };
  itemsPerPage?: number;
}

const props = withDefaults(defineProps<Props>(), {
  config: () => ({ theme: 'light' }),
  itemsPerPage: 10
});
</script>

withDefaults pour les valeurs par defaut typees

Regles importantes

  1. Types primitifs : Definis directement
  2. Objets et tableaux : Fonction factory obligatoire
  3. Types union : Valeur par defaut du bon type
<script setup lang="ts">
type ButtonVariant = 'primary' | 'secondary' | 'danger';

interface Props {
  variant?: ButtonVariant;
  disabled?: boolean;
  options?: Record<string, unknown>;
}

const props = withDefaults(defineProps<Props>(), {
  variant: 'primary',
  disabled: false,
  options: () => ({})
});
</script>

Typage des emits avec defineEmits

Syntaxe basee sur les types

<script setup lang="ts">
interface User {
  id: number;
  name: string;
}

const emit = defineEmits<{
  (e: 'update', value: string): void;
  (e: 'select', user: User): void;
  (e: 'cancel'): void;
}>();

function handleSelect(selectedUser: User) {
  emit('select', selectedUser);
}
</script>

Syntaxe alternative (Vue 3.3+)

<script setup lang="ts">
const emit = defineEmits<{
  update: [value: string];
  select: [user: User];
  cancel: [];
}>();
</script>

Props de type fonction

<script setup lang="ts">
interface Props {
  onClick?: () => void;
  formatter?: (value: number) => string;
  validator?: (value: string) => boolean;
}

const props = withDefaults(defineProps<Props>(), {
  onClick: () => {},
  formatter: (value) => value.toString(),
  validator: () => true
});
</script>

Props fonction avec types generiques

<script setup lang="ts" generic="T">
interface Props {
  items: T[];
  keyExtractor: (item: T) => string | number;
  filterFn?: (item: T) => boolean;
}

const props = defineProps<Props>();

const filteredItems = computed(() => {
  if (props.filterFn) {
    return props.items.filter(props.filterFn);
  }
  return props.items;
});
</script>

Props de type objet complexe

Interfaces imbriquees

<script setup lang="ts">
interface Address {
  street: string;
  city: string;
}

interface User {
  id: number;
  firstName: string;
  lastName: string;
  contact: { email: string; address?: Address };
  preferences: { theme: 'light' | 'dark' };
}

interface Props {
  user: User;
  editable?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
  editable: false
});

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

Utilisation de types utilitaires

<script setup lang="ts">
interface Product {
  id: number;
  name: string;
  price: number;
  stock: number;
}

type ProductPreview = Pick<Product, 'id' | 'name' | 'price'>;
type ProductUpdate = Partial<Omit<Product, 'id'>>;

interface Props {
  product?: Product;
  preview?: ProductPreview;
  mode: 'view' | 'edit' | 'create';
}

const props = defineProps<Props>();
</script>

Props avec types union discrimines

<script setup lang="ts">
type LoadingState = { status: 'loading' };
type SuccessState<T> = { status: 'success'; data: T };
type ErrorState = { status: 'error'; error: string };

type DataState<T> = LoadingState | SuccessState<T> | ErrorState;

interface Props {
  userState: DataState<User>;
}

const props = defineProps<Props>();

function renderContent() {
  switch (props.userState.status) {
    case 'loading': return 'Chargement...';
    case 'success': return props.userState.data.name;
    case 'error': return `Erreur: ${props.userState.error}`;
  }
}
</script>

Tableau comparatif Options API vs Composition API

FonctionnaliteOptions APIComposition API
Definitionprops: { ... }defineProps<T>()
TypagePropType<T>Interface generique
Valeurs par defautdefault: valuewithDefaults()
Validation runtimevalidator: fnNon disponible
Requiredrequired: truePropriete non-optionnelle
Accesthis.propNameprops.propName

Bonnes pratiques

1. Toujours definir des interfaces explicites

// Mauvais
const props = defineProps<{
  user: { id: number; name: string };
}>();

// Bon
interface User { id: number; name: string; }
const props = defineProps<{ user: User }>();

2. Utiliser des fichiers de types partages

// types/index.ts
export interface User { id: number; name: string; }

// Composant
import type { User } from '@/types';

3. Preferer les types stricts

// Mauvais
interface Props { status: string; }

// Bon
interface Props { status: 'pending' | 'active' | 'completed'; }

4. Documenter avec JSDoc

interface Props {
  /** Configuration du tableau @default { striped: true } */
  config?: TableConfig;
}

5. Regrouper les props optionnelles

// Mauvais
interface Props { title?: string; icon?: string; color?: string; }

// Bon
interface CardConfig { title?: string; icon?: string; }
interface Props { content: CardConfig; }

6. Utiliser readonly

interface Props {
  readonly config: Readonly<Config>;
  readonly items: ReadonlyArray<Item>;
}

Pieges courants a eviter

1. Oublier la fonction factory

// ERREUR
withDefaults(defineProps<Props>(), { items: [] });

// CORRECT
withDefaults(defineProps<Props>(), { items: () => [] });

2. Destructurer les props sans toRefs

// ERREUR - perte de reactivite
const { user } = props;

// CORRECT
const { user } = toRefs(props);

3. Modifier les props directement

// ERREUR
props.user.name = 'Nouveau nom';

// CORRECT
emit('update:user', { ...props.user, name: 'Nouveau nom' });

4. Types trop larges

// ERREUR
interface Props { data: any; callback: Function; }

// CORRECT
interface Props { data: UserData; callback: (result: Result) => void; }

5. Ignorer les props undefined

// ERREUR
const fullName = props.user.firstName + ' ' + props.user.lastName;

// CORRECT
const fullName = computed(() =>
  props.user ? `${props.user.firstName} ${props.user.lastName}` : ''
);

Conclusion

Le typage des props dans Vue.js avec TypeScript est une competence fondamentale pour construire des applications robustes :

  • PropType pour l’Options API offre une validation runtime
  • defineProps generique avec la Composition API fournit une syntaxe plus concise
  • withDefaults permet de definir des valeurs par defaut type-safe
  • defineEmits assure le typage complet de la communication parent-enfant

Les bonnes pratiques couvertes - interfaces explicites, types stricts, documentation JSDoc - vous aideront a maintenir un code de qualite. En evitant les pieges courants, vous reduirez considerablement les bugs.

L’investissement dans un typage rigoureux se traduit par une meilleure autocompletion, une documentation vivante et une detection precoce des erreurs. C’est un element cle pour des applications Vue.js professionnelles et maintenables.

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