Props
Cette page suppose que vous avez déjà lu les principes fondamentaux des composants. Lisez-les d'abord si vous débutez avec les composants.
Déclaration de props
Les composants Vue nécessitent une déclaration explicite des props afin que Vue sache quels props externes passés au composant doivent être traités comme des attributs implicitement déclarés (qui seront discutés dans sa section dédiée).
Dans les SFC utilisant <script setup>
, les props peuvent être déclarés à l'aide de la macro defineProps()
:
vue
<script setup>
const props = defineProps(['foo'])
console.log(props.foo)
</script>
Dans les composants autres que <script setup>
, les props sont déclarés à l'aide de l'option props
:
js
export default {
props: ['foo'],
setup(props) {
// setup() reçoit des props comme premier argument.
console.log(props.foo)
}
}
Notez que l'argument passé à defineProps()
est le même que la valeur fournie à l'option props
: la même API de déclaration de props est partagée entre les deux styles de déclaration.
En plus de déclarer des props à l'aide d'un tableau de chaînes de caractères, nous pouvons également utiliser la syntaxe utilisant un objet :
js
// dans <script setup>
defineProps({
title: String,
likes: Number
})
js
// dans un fichier autre que <script setup>
export default {
props: {
title: String,
likes: Number
}
}
Pour chaque propriété dans l'objet servant de déclaration, la clé est le nom de la prop, tandis que la valeur doit être la fonction constructeur du type attendu.
Cela documente non seulement votre composant, mais avertira également, dans la console du navigateur, les autres développeurs utilisant votre composant s'ils transmettent le mauvais type. Nous discuterons plus en détail de la validation de prop plus loin sur cette page.
Si vous utilisez TypeScript avec <script setup>
, il est également possible de déclarer des props en utilisant uniquement des annotations de type :
vue
<script setup lang="ts">
defineProps<{
title?: string
likes?: number
}>()
</script>
Plus de détails: Typage de props de composant
Reactive Props Destructure
Vue's reactivity system tracks state usage based on property access. E.g. when you access props.foo
in a computed getter or a watcher, the foo
prop gets tracked as a dependency.
So, given the following code:
js
const { foo } = defineProps(['foo'])
watchEffect(() => {
// runs only once before 3.5
// re-runs when the "foo" prop changes in 3.5+
console.log(foo)
})
In version 3.4 and below, foo
is an actual constant and will never change. In version 3.5 and above, Vue's compiler automatically prepends props.
when code in the same <script setup>
block accesses variables destructured from defineProps
. Therefore the code above becomes equivalent to the following:
js
const props = defineProps(['foo'])
watchEffect(() => {
// `foo` transformed to `props.foo` by the compiler
console.log(props.foo)
})
In addition, you can use JavaScript's native default value syntax to declare default values for the props. This is particularly useful when using the type-based props declaration:
ts
const { foo = 'hello' } = defineProps<{ foo?: string }>()
If you prefer to have more visual distinction between destructured props and normal variables in your IDE, Vue's VSCode extension provides a setting to enable inlay-hints for destructured props.
Passing Destructured Props into Functions
When we pass a destructured prop into a function, e.g.:
js
const { foo } = defineProps(['foo'])
watch(foo, /* ... */)
This will not work as expected because it is equivalent to watch(props.foo, ...)
- we are passing a value instead of a reactive data source to watch
. In fact, Vue's compiler will catch such cases and throw a warning.
Similar to how we can watch a normal prop with watch(() => props.foo, ...)
, we can watch a destructured prop also by wrapping it in a getter:
js
watch(() => foo, /* ... */)
In addition, this is the recommended approach when we need to pass a destructured prop into an external function while retaining reactivity:
js
useComposable(() => foo)
The external function can call the getter (or normalize it with toValue) when it needs to track changes of the provided prop, e.g. in a computed or watcher getter.
Détails sur le passage de props
Casse du nom de prop
Nous déclarons les noms de props en utilisant le camelCase car cela évite d'avoir à utiliser des guillemets lors de leur utilisation comme clés de propriété, et nous permet de les référencer directement dans les expressions de template car ce sont des identifiants JavaScript valides :
js
defineProps({
greetingMessage: String
})
template
<span>{{ greetingMessage }}</span>
Techniquement, vous pouvez également utiliser le camelCase lors de la transmission de props à un composant enfant (sauf dans les templates DOM). Cependant, la convention utilise le kebab-case dans tous les cas pour s'aligner sur les attributs HTML :
template
<MyComponent greeting-message="hello" />
Nous utilisons le PascalCase pour les balises de composant lorsque cela est possible, car il améliore la lisibilité du template en différenciant les composants Vue des éléments natifs. Cependant, il n'y a pas autant d'avantages pratiques à utiliser le camelCase lors du passage de props, nous avons donc choisi de suivre les conventions de chaque langage.
Props statiques vs. dynamiques
Jusqu'à présent, vous avez vu des props passés en tant que valeurs statiques, comme dans :
template
<BlogPost title="My journey with Vue" />
Vous avez également vu des props assignés dynamiquement avec v-bind
ou son raccourci :
, comme dans :
template
<!-- Attribuer dynamiquement la valeur d'une variable -->
<BlogPost :title="post.title" />
<!-- Attribuer dynamiquement la valeur d'une expression complexe -->
<BlogPost :title="post.title + ' by ' + post.author.name" />
Passage de différents types de valeur
Dans les deux exemples ci-dessus, nous passons des chaînes de caractères, mais n'importe quel type de valeur peut être passé à une prop.
Nombre
template
<!-- Même si `42` est statique, nous avons besoin de v-bind pour indiquer à Vue qu'il -->
<!-- s'agit d'une expression JavaScript plutôt que d'une chaîne de caractères. -->
<BlogPost :likes="42" />
<!-- Attribuer dynamiquement la valeur d'une variable. -->
<BlogPost :likes="post.likes" />
Booléen
template
<!-- Inclure la prop sans valeur impliquera `true`. -->
<BlogPost is-published />
<!-- Même si `false` est statique, nous avons besoin de v-bind pour indiquer à Vue qu'il -->
<!-- s'agit d'une expression JavaScript plutôt que d'une chaîne de caractères. -->
<BlogPost :is-published="false" />
<!-- Attribuer dynamiquement la valeur d'une variable. -->
<BlogPost :is-published="post.isPublished" />
Tableau
template
<!-- Même si le tableau est statique, nous avons besoin de v-bind pour indiquer à Vue qu'il -->
<!-- s'agit d'une expression JavaScript plutôt que d'une chaîne de caractères. -->
<BlogPost :comment-ids="[234, 266, 273]" />
<!-- Attribuer dynamiquement la valeur d'une variable. -->
<BlogPost :comment-ids="post.commentIds" />
Objet
template
<!-- Même si l'objet est statique, nous avons besoin de v-bind pour indiquer à Vue qu'il -->
<!-- s'agit d'une expression JavaScript plutôt que d'une chaîne de caractères. -->
<BlogPost
:author="{
name: 'Veronica',
company: 'Veridian Dynamics'
}"
/>
<!-- Attribuer dynamiquement la valeur d'une variable. -->
<BlogPost :author="post.author" />
Liaison de plusieurs propriétés à l'aide d'un objet
Si vous souhaitez transmettre toutes les propriétés d'un objet en tant que props, vous pouvez utiliser v-bind
sans argument (v-bind
au lieu de :prop-name
). Par exemple, étant donné un objet post
:
js
const post = {
id: 1,
title: 'My Journey with Vue'
}
Le template suivant :
template
<BlogPost v-bind="post" />
Sera équivalent à :
template
<BlogPost :id="post.id" :title="post.title" />
Flux de données à sens unique
Tous les props forment une liaison unidirectionnelle entre la propriété enfant et la propriété parent : lorsque la propriété parent est mise à jour, elle descendra vers l'enfant, mais pas l'inverse. Cela empêche les composants enfants de muter accidentellement l'état du parent, ce qui peut rendre le flux de données de votre application plus difficile à comprendre.
De plus, chaque fois que le composant parent est mis à jour, tous les props du composant enfant seront actualisés avec la dernière valeur. Cela signifie que vous ne devez pas tenter de muter une prop à l'intérieur d'un composant enfant. Si vous le faites, Vue vous avertira dans la console :
js
const props = defineProps(['foo'])
// ❌ avertissement, les props sont en lecture seule !
props.foo = 'bar'
Il y a généralement deux cas où il est tentant de muter une prop :
La prop est utilisée pour passer une valeur initiale; le composant enfant veut ensuite l'utiliser comme propriété de données locale. Dans ce cas, il est préférable de définir une propriété de données locale qui utilise la prop comme valeur initiale :
jsconst props = defineProps(['initialCounter']) // counter utilise uniquement props.initialCounter comme valeur initiale ; // il est déconnecté des futures mises à jour de props. const counter = ref(props.initialCounter)
La prop est transmise en tant que valeur brute qui doit être transformée. Dans ce cas, il est préférable de définir une propriété calculée à l'aide de la valeur de la prop :
jsconst props = defineProps(['size']) // propriété calculée qui se met à jour automatiquement lorsque la prop change const normalizedSize = computed(() => props.size.trim().toLowerCase())
Mutation de props de type objet et tableau
Lorsque des objets et des tableaux sont passés en tant que props, bien que le composant enfant ne puisse pas muter la liaison de la prop, il pourra muter les propriétés imbriquées de l'objet ou du tableau. En effet, en JavaScript, les objets et les tableaux sont passés par référence, et il est déraisonnablement coûteux pour Vue d'empêcher de telles mutations.
Le principal inconvénient de ces mutations est qu'elles permettent au composant enfant de modifier l'état parent d'une manière qui n'est pas évidente vis-à-vis du composant parent, ce qui rend potentiellement plus difficile la compréhension du flux de données et sa maintenabilité dans le futur. En tant que bonne pratique, vous devez éviter de telles mutations à moins que le parent et l'enfant ne soient étroitement couplés par conception. Dans la plupart des cas, l'enfant doit émettre un événement pour laisser le parent effectuer la mutation.
Validation de prop
Les composants peuvent spécifier des exigences pour leurs props, tels que la déclaration des types que vous avez déjà vus. Si une exigence n'est pas remplie, Vue vous avertira dans la console JavaScript du navigateur. Ceci est particulièrement utile lors du développement d'un composant destiné à être utilisé par d'autres.
Pour spécifier les validations de props, vous pouvez fournir un objet avec des exigences de validation à la macro defineProps()
, au lieu d'un tableau de chaînes de caractères. Par exemple :
js
defineProps({
// Contrôle de type de base
// (les valeurs `null` et `undefined` autoriseront tous les types)
propA: Number,
// Plusieurs types possibles
propB: [String, Number],
// Chaîne de caractères requise
propC: {
type: String,
required: true
},
// Obligatoire, mais chaîne de caractères nulle
propD: {
type: [String, null],
required: true
},
// Nombre avec une valeur par défaut
propE: {
type: Number,
default: 100
},
// Objet avec une valeur par défaut
propF: {
type: Object,
// Les valeurs par défaut d'un objet ou d'un tableau doivent être renvoyées à partir
// d'une fonction factory. La fonction reçoit les props bruts
// reçus par le composant en tant qu'argument.
default(rawProps) {
return { message: 'hello' }
}
},
// Fonction de validation personnalisée
// Toutes les props passées dans le 2nd argument depuis la 3.4
propG: {
validator(value, props) {
// La valeur doit correspondre à l'une de ces chaînes de caractères
return ['success', 'warning', 'danger'].includes(value)
}
},
// Fonction avec une valeur par défaut
propH: {
type: Function,
// Contrairement aux valeurs par défaut d'un objet ou d'un tableau, il ne s'agit pas d'une fonction
// factory - il s'agit d'une fonction servant de valeur par défaut
default() {
return 'Default function'
}
}
})
TIP
Le code à l'intérieur de l'argument defineProps()
ne peut pas accéder aux autres variables déclarées dans <script setup>
, car l'expression entière est déplacée vers une portée de fonction externe lors de la compilation.
Détails supplémentaires :
Toutes les props sont facultatives par défaut, sauf si
required: true
est spécifié.Une prop facultative absente autre que
Boolean
aura la valeurundefined
.Les props
Boolean
absentes seront converties enfalse
. Vous pouvez changer cela en définissant une valeur par défaut, c'est-à-dire:default: undefined
pour se comporter comme une prop non booléen.Si une valeur
default
est spécifiée, elle sera utilisée si la valeur prop résolue estundefined
- cela inclut à la fois lorsque la prop est absente ou lorsqu'une valeurundefined
explicite est passée.
Lorsque la validation de prop échoue, Vue produira un avertissement dans la console (si vous utilisez la version de développement).
Si vous utilisez des déclarations de props basées sur les types , Vue fera de son mieux pour compiler les annotations de type dans des déclarations de props équivalentes lors de l'exécution. Par exemple, defineProps<{ msg: string }>
sera compilé en { msg: { type: String, required: true }}
.
Vérifications de type à l'exécution
Le type
peut être l'un des constructeurs natifs suivants :
String
Number
Boolean
Array
Object
Date
Function
Symbol
Error
De plus, type
peut également être une classe personnalisée ou une fonction constructeur et l'assertion sera faite avec une vérification instanceof
. Par exemple, étant donné la classe suivante :
js
class Person {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
}
Vous pouvez l'utiliser comme type de prop :
js
defineProps({
author: Person
})
Vue utilisera instanceof Person
pour valider si la valeur de la prop author
est bien une instance de la classe Person
.
Type Nullable
Si le type est obligatoire mais nullable, vous pouvez utiliser la syntaxe des tableaux qui inclut null
:
js
defineProps({
id: {
type: [String, null],
required: true
}
})
Notez que si type
est juste null
sans utiliser la syntaxe du tableau, il autorisera n'importe quel type.
Conversion en booléen
Les props avec le type Boolean
ont des règles de conversion spéciales pour imiter le comportement des attributs booléens natifs. Admettons un composant <MyComponent>
avec la déclaration suivante :
js
defineProps({
disabled: Boolean
})
Le composant peut être utilisé comme ceci :
template
<!-- équivalent à passer :disabled="true" -->
<MyComponent disabled />
<!-- équivalent à passer :disabled="false" -->
<MyComponent />
Lorsqu'une prop est déclarée pour autoriser plusieurs types, les règles de conversion pour Boolean
seront également appliquées. Cependant, il y a un avantage lorsque String
et Boolean
sont autorisés - la règle de casting sur Boolean ne s'applique que si Boolean apparaît avant String :
js
// disabled sera transformé en true
defineProps({
disabled: [Boolean, Number]
})
// disabled sera transformé en true
defineProps({
disabled: [Boolean, String]
})
// disabled sera transformé en true
defineProps({
disabled: [Number, Boolean]
})
// disabled sera analysé comme une chaîne vide (disabled="")
defineProps({
disabled: [String, Boolean]
})