Component Architecture
Thème :
Junior
1 vues ·8/11 leçonsSchémas Avancés de Provide/Inject
Explorons des schémas pratiques pour utiliser provide/inject dans des applications réelles.
Schéma de Contexte d’Authentification
Partagez l’état d’authentification dans toute votre application :
// src/composables/useAuth.ts
import { ref, computed, readonly } from 'vue';
import type { InjectionKey, Ref } from 'vue';
export type User = {
id: number;
name: string;
email: string;
role: 'admin' | 'user';
};
export type AuthContext = {
user: Readonly<Ref<User | null>>;
isAuthenticated: Ref<boolean>;
isAdmin: Ref<boolean>;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
};
export const AuthKey: InjectionKey<AuthContext> = Symbol('auth');
export function createAuthContext(): AuthContext {
const user = ref<User | null>(null);
const isAuthenticated = computed(() => user.value !== null);
const isAdmin = computed(() => user.value?.role === 'admin');
async function login(email: string, password: string) {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
user.value = await response.json();
}
function logout() {
user.value = null;
}
return {
user: readonly(user),
isAuthenticated,
isAdmin,
login,
logout,
};
}
<!-- App.vue -->
<script setup lang="ts">
import { provide } from 'vue';
import { AuthKey, createAuthContext } from '@/composables/useAuth';
const auth = createAuthContext();
provide(AuthKey, auth);
</script>
<!-- N'importe quel composant -->
<script setup lang="ts">
import { inject } from 'vue';
import { AuthKey } from '@/composables/useAuth';
const auth = inject(AuthKey)!;
</script>
<template>
<div v-if="auth.isAuthenticated">
Bienvenue, {{ auth.user?.name }}
<button @click="auth.logout">Déconnexion</button>
</div>
<LoginForm v-else @login="auth.login" />
</template>
Système de Notifications Toast
// src/composables/useToast.ts
import { ref, readonly } from 'vue';
import type { InjectionKey, Ref } from 'vue';
export type Toast = {
id: number;
message: string;
type: 'success' | 'error' | 'warning' | 'info';
duration?: number;
};
export type ToastContext = {
toasts: Readonly<Ref<Toast[]>>;
show: (message: string, type?: Toast['type'], duration?: number) => void;
success: (message: string) => void;
error: (message: string) => void;
warning: (message: string) => void;
info: (message: string) => void;
dismiss: (id: number) => void;
};
export const ToastKey: InjectionKey<ToastContext> = Symbol('toast');
export function createToastContext(): ToastContext {
const toasts = ref<Toast[]>([]);
let nextId = 1;
function show(message: string, type: Toast['type'] = 'info', duration = 5000) {
const id = nextId++;
toasts.value.push({ id, message, type, duration });
if (duration > 0) {
setTimeout(() => dismiss(id), duration);
}
}
function dismiss(id: number) {
const index = toasts.value.findIndex((t) => t.id === id);
if (index > -1) toasts.value.splice(index, 1);
}
return {
toasts: readonly(toasts),
show,
success: (msg) => show(msg, 'success'),
error: (msg) => show(msg, 'error'),
warning: (msg) => show(msg, 'warning'),
info: (msg) => show(msg, 'info'),
dismiss,
};
}
<!-- ToastContainer.vue -->
<script setup lang="ts">
import { inject } from 'vue';
import { ToastKey } from '@/composables/useToast';
const toast = inject(ToastKey)!;
</script>
<template>
<div class="toast-container">
<TransitionGroup name="toast">
<div v-for="t in toast.toasts" :key="t.id" :class="['toast', t.type]" @click="toast.dismiss(t.id)">
{{ t.message }}
</div>
</TransitionGroup>
</div>
</template>
Schéma de Contexte de Formulaire
Partagez l’état d’un formulaire avec des champs imbriqués :
<!-- FormProvider.vue -->
<script setup lang="ts">
import { provide, reactive, computed } from 'vue';
import type { InjectionKey } from 'vue';
type FormContext = {
values: Record<string, any>;
errors: Record<string, string>;
touched: Record<string, boolean>;
setValue: (name: string, value: any) => void;
setError: (name: string, error: string) => void;
setTouched: (name: string) => void;
isValid: boolean;
};
export const FormKey: InjectionKey<FormContext> = Symbol('form');
const props = defineProps<{
initialValues?: Record<string, any>;
}>();
const emit = defineEmits<{
submit: [values: Record<string, any>];
}>();
const state = reactive({
values: { ...props.initialValues },
errors: {} as Record<string, string>,
touched: {} as Record<string, boolean>,
});
const isValid = computed(() => Object.keys(state.errors).length === 0);
provide(FormKey, {
values: state.values,
errors: state.errors,
touched: state.touched,
setValue: (name, value) => {
state.values[name] = value;
},
setError: (name, error) => {
if (error) state.errors[name] = error;
else delete state.errors[name];
},
setTouched: (name) => {
state.touched[name] = true;
},
isValid: isValid.value,
});
function handleSubmit() {
if (isValid.value) {
emit('submit', state.values);
}
}
</script>
<template>
<form @submit.prevent="handleSubmit">
<slot></slot>
</form>
</template>
<!-- FormInput.vue -->
<script setup lang="ts">
import { inject, computed } from 'vue';
import { FormKey } from './FormProvider.vue';
const props = defineProps<{
name: string;
label: string;
rules?: (value: any) => string | true;
}>();
const form = inject(FormKey)!;
const value = computed({
get: () => form.values[props.name] || '',
set: (val) => {
form.setValue(props.name, val);
validate(val);
},
});
function validate(val: any) {
if (props.rules) {
const result = props.rules(val);
form.setError(props.name, result === true ? '' : result);
}
}
function handleBlur() {
form.setTouched(props.name);
}
</script>
<template>
<div class="form-group">
<label :for="name">{{ label }}</label>
<input :id="name" v-model="value" @blur="handleBlur" :class="{ error: form.touched[name] && form.errors[name] }" />
<span v-if="form.touched[name] && form.errors[name]" class="error-text">
{{ form.errors[name] }}
</span>
</div>
</template>
<!-- Utilisation -->
<FormProvider :initial-values="{ email: '', password: '' }" @submit="handleSubmit">
<FormInput
name="email"
label="Email"
:rules="v => !v ? 'Requis' : true"
/>
<FormInput
name="password"
label="Mot de passe"
:rules="v => v.length < 8 ? '8 caractères minimum' : true"
/>
<button type="submit">Soumettre</button>
</FormProvider>
Ressources
- Travailler avec la Réactivité — Rendre provide/inject réactif
📘 Cette leçon fait partie du cours Architecture des Composants Vue sur Stanza — la plateforme d’apprentissage native à l’IDE pour les développeurs.
Prêt à passer à la suite ?
Vous avez terminé cette leçon sur Schémas Avancés de Provide/Inject. Continuez votre apprentissage avec la prochaine leçon ou explorez d'autres formations.