Comment utiliser la syntaxe `script setup` en Vue 3
Vue 3.2 a introduit la syntaxe script setup
,
une façon un peu moins verbeuse de déclarer les composants.
On l’active en ajoutant l’attribut setup
à la balise script
d’un SFC,
et on peut alors enlever un peu de boilerplate du composant.
Prenons un exemple pratique, et migrons-le vers cette syntaxe !
Migrer un composant
Le composant Pony
ci-dessous a deux props (le ponyModel
à afficher, et une option isRunning
).
Basée sur ces deux props, une URL est calculée pour l’image du poney affichée dans le template
(via un autre composant Image
).
Le composant émet également un événement selected
quand on clique dessus.
Pony.vue
<template>
<figure @click="clicked()">
<Image :src="ponyImageUrl" :alt="ponyModel.name" />
<figcaption>{{ ponyModel.name }}</figcaption>
</figure>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import Image from './Image.vue';
import { PonyModel } from '@/models/PonyModel';
export default defineComponent({
components: { Image },
props: {
ponyModel: {
type: Object as PropType<PonyModel>,
required: true
},
isRunning: {
type: Boolean,
default: false
}
},
emits: {
selected: () => true
},
setup(props, { emit }) {
const ponyImageUrl = computed(() => `/pony-${props.ponyModel.color}${props.isRunning ? '-running' : ''}.gif`);
function clicked() {
emit('selected');
}
return { ponyImageUrl, clicked };
}
});
</script>
Première étape : ajoutons l’attribut setup
à l’élément script
.
On peut alors garder seulement le contenu de la fonction setup
,
et supprimer l’appel à defineComponent
et setup
:
Pony.vue
<script setup lang="ts">
import { computed, PropType } from 'vue';
import Image from './Image.vue';
import { PonyModel } from '@/models/PonyModel';
components: { Image },
props: {
ponyModel: {
type: Object as PropType<PonyModel>,
required: true
},
isRunning: {
type: Boolean,
default: false
}
},
emits: {
selected: () => true
},
const ponyImageUrl = computed(() => `/pony-${props.ponyModel.color}${props.isRunning ? '-running' : ''}.gif`);
function clicked() {
emit('selected');
}
return { ponyImageUrl, clicked };
</script>
Retour implicite
On peut également supprimer le return
à la fin :
toutes les déclarations de haut niveau à l’intérieur d’un script setup
(ainsi que les imports)
sont automatiquement disponible dans le template.
Ici ponyImageUrl
et clicked
sont disponibles sans avoir besoin de les renvoyer.
C’est la même chose pour les components
!
Importer le composant Image
est suffisant,
et Vue comprend qu’il est utilisé dans le template.
On peut donc enlever la déclaration components
.
Pony.vue
<script setup lang="ts">
import { computed, PropType } from 'vue';
import Image from './Image.vue';
import { PonyModel } from '@/models/PonyModel';
props: {
ponyModel: {
type: Object as PropType<PonyModel>,
required: true
},
isRunning: {
type: Boolean,
default: false
}
},
emits: {
selected: () => true
},
const ponyImageUrl = computed(() => `/pony-${props.ponyModel.color}${props.isRunning ? '-running' : ''}.gif`);
function clicked() {
emit('selected');
}
</script>
On y est presque :
il reste à migrer les déclarations props
et emits
.
defineProps
Vue nous donne une fonction defineProps
que l’on peut utiliser pour déclarer les props.
C’est une fonction disponible à la compilation (une macro),
il n’y a donc pas besoin de l’importer dans le code :
Vue comprend tout seul lorsqu’il compile le composant.
defineProps
renvoie les props :
const props = defineProps({
ponyModel: {
type: Object as PropType<PonyModel>,
required: true
},
isRunning: {
type: Boolean,
default: false
}
});
defineProps
reçoit la même déclaration que props
comme paramètre.
Mais on peut faire encore mieux pour les utilisateurs TypeScript !
defineProps
est typé de façon générique :
on peut l’appeler sans paramètre,
mais en spécifiant une interface comme “forme” des props.
Plus besoin de l’horrible Object as PropType<Something>
!
On peut utiliser de vrais types TypeScript,
et ajouter ?
pour marquer une prop comme optionnelle 😍.
const props = defineProps<{
ponyModel: PonyModel;
isRunning?: boolean;
}>();
Nous avons perdu un bout d’information cependant.
Dans la version précédente, nous pouvions spécifier que isRunning
avait une valeur par défaut à false
.
Pour avoir le même comportement, on peut utiliser la fonction withDefaults
:
interface Props {
ponyModel: PonyModel;
isRunning?: boolean;
}
const props = withDefaults(defineProps<Props>(), { isRunning: false });
La dernière partie à migrer est la déclaration des événements.
defineEmits
Vue nous offre également une fonction defineEmits
,
très similaire à defineProps
.
defineEmits
renvoie la fonction emit
:
const emit = defineEmits({
selected: () => true
});
Ou encore mieux, avec TypeScript :
const emit = defineEmits<{
(e: 'selected'): void;
}>();
La déclaration complète est plus courte de 10 lignes, ce qui est pas mal pour un composant d’une trentaine de lignes ! Le composant est plus simple à lire, et plus facile à écrire en TypeScript. Il est cependant un peu étrange de voir tout exposé automatiquement dans le template, sans avoir à écrire de return, mais on s’habitue.
Pony.vue
<template>
<figure @click="clicked()">
<Image :src="ponyImageUrl" :alt="ponyModel.name" />
<figcaption>{{ ponyModel.name }}</figcaption>
</figure>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import Image from './Image.vue';
import { PonyModel } from '@/models/PonyModel';
interface Props {
ponyModel: PonyModel;
isRunning?: boolean;
}
const props = withDefaults(defineProps<Props>(), { isRunning: false });
const emit = defineEmits<{
(e: 'selected'): void;
}>();
const ponyImageUrl = computed(() => `/pony-${props.ponyModel.color}${props.isRunning ? '-running' : ''}.gif`);
function clicked() {
emit('selected');
}
</script>
Fermé par défaut et defineExpose
Il y a une différence subtile entre les deux façons d’écrire des composants :
un composant script setup
est “fermé par défaut”.
Cela veut dire que d’autres composants ne voit pas ce qui est défini à l’intérieur de celui-ci.
Par exemple, le composant Pony
peut accéder au composant Image
qu’il utilise
(en utilisant des refs, comme on le verra dans un chapitre plus loin).
Si Image
est déclaré avec defineComponent
,
alors tout ce que renvoie sa fonction setup
est aussi visible par le composant parent (Pony
).
En revanche, si Image
est défini avec script setup
,
alors rien n’est visible pour le composant parent.
Image
peut cependant exposer ce qu’il souhaite en ajoutant defineExpose({ key: value })
.
Alors value
sera accessible comme étant key
.
Cette syntaxe est donc celle qui est recommandée pour construire tes composants, et elle est très cool à utiliser !
Notre ebook, formation en ligne et formation sont à jour avec ces changements si vous voulez en apprendre plus !