Vue3 propose une méthode simple pour lier des formulaires via v-model, mais lorsque vous créez vos propres composants ou封装 des bibliothèques UI, cela peut devenir complexe. Voici une approche pour unifier le processus de liaison des formulaires en utilisant TypeScript, tout en intégrent des fonctionnalités comme le debounce.
Utilisation de base Vue3 offre v-model pour une liaison simple. Par exemple, v-model="nom" est suffisant pour la plupart des cas.
Création d'un composant personnalisé Pour créer un composant personnalisé, vous devez gérer les props, les émissions et les événements input :
<script setup>
defineProps(['valeur'])
defineEmits(['update:valeur'])
</script>
<template>
<input
:value="valeur"
@input="$emit('update:valeur', $event.target.value)"
/>
</template>
Emballage des composants de bibliothèques UI Lorsque vous封装 des composants comme el-input, vous devez utiliser des computés pour gérer la liaison :
<script setup>
import { computed } from 'vue'
const props = defineProps(['valeur'])
const emit = defineEmits(['update:valeur'])
const valeurInterne = computed({
get() {
return props.valeur
},
set(valeur) {
emit('update:valeur', valeur)
}
})
</script>
<template>
<el-input v-model="valeurInterne" />
</template>
Pour implémenter le debounce, le code doit être ajusté :
import { customRef, watch } from 'vue'
export function refEmit<T, K extends keyof T>(
props: T,
emit: (nom: string, ...args: any[]) => void,
cle: K
) {
return customRef<T[K]>((suivre, actualiser) => {
return {
get() {
suivi()
return props[cle]
},
set(valeur: T[K]) {
actualisation()
emit(`update:${cle.toString()}`, valeur)
}
}
})
}
Implémentation du debounce Voici une version avec debounce :
import { customRef, watch } from 'vue'
export function refDebounce<T, K extends keyof T>(
props: T,
emit: (nom: string, ...args: any[]) => void,
cle: K,
delai = 500
) {
let timeout: NodeJS.Timeout
let valeurCourante: T[K] = props[cle]
return customRef<T[K]>((suivre, actualiser) => {
watch(() => props[cle], (nouvelleValeur) => {
valeurCourante = nouvelleValeur
actualiser()
})
return {
get() {
suivi()
return valeurCourante
},
set(nouvelleValeur: T[K]) {
valeurCourante = nouvelleValeur
actualiser()
clearTimeout(timeout)
timeout = setTimeout(() => {
emit(`update:${cle.toString()}`, nouvelleValeur)
}, delai)
}
}
})
}
Transmission directe du modèle Au lieu de lier chaque champ individuellement, vous pouvez transmettre un objet modèle entier :
import { computed } from 'vue'
export function refModele<T, K extends keyof T>(modele: T, nomColonne: K) {
return computed<T[K]>({
get() {
return modele[nomColonne]
},
set(valeur: T[K]) {
modele[nomColonne] = valeur
}
})
}
Emballage pour plusieurs champs Pour des champs comme des dates de début et de fin, vous pouvez créer un emballage qui gère plusieurs champs :
import { customRef } from 'vue'
interface IModele {
[cle: string]: any
}
export function refPlage<T extends IModele, K extends keyof T>(
modele: T,
...colonnes: K[]
) {
return customRef<Array<any>>((suivre, actualiser) => {
return {
get() {
const valeurs: Array<any> = []
colonnes.forEach(cle => {
valeurs.push(modele[cle])
})
return valeurs
},
set(valeurs: Array<any>) {
if (valeurs) {
colonnes.forEach((cle, index) => {
modele[cle] = index < valeurs.length ? valeurs[index] : ''
})
} else {
colonnes.forEach(cle => {
modele[cle] = ''
})
}
}
}
})
}
Utilisation dans les composants Dans un composant parenet :
const valeurEmit = ref('')
const personne = reactive({ nom: 'Test', age: 111 })
const plageDate = reactive({ d1: '2012-10-11', d2: '2012-11-11' })
Dans le template :
<emit-exemple v-model="valeurEmit"/>
<modele-exemple :modele="personne" nomColonne="nom"/>
<plage-exemple :modele="plageDate" nomColonne="d1_d2"/>
Comparaison des approches
| Caractéristique | Émission | Modèle |
|---|---|---|
| Typage explicite | Difficile | Très clair |
| Paramètres (utilisation) | Un seul | Deux |
| Efficacité | Interne besoins de transfert | Modification directe de l'objet |
| Utilisation dans les composants | Doit définir les émissions | Ne doit pas définir les émissions |