Vzory komponent a composable v Vue: Průvodce začátečníka
Tento průvodce poskytuje praktický přehled vzorů návrhu komponent a composable v Vue s tipy a příklady, přizpůsobený vývojářům ve vašem startupu. Využívá poznatky z různých zdrojů, které vám pomohou psát čistší, udržitelnější a lépe škálovatelný kód Vue.
Vzory návrhu komponent
1. Vzor komponent
Extrahování znovupoužitelných komponent z existujících komponent zjednodušuje kód a zvyšuje znovupoužitelnost. To podporuje Princip jediné odpovědnosti, čímž je váš kódový základ modulárnější a snadněji udržitelný.
Tip: Identifikujte a extrahujte skryté komponenty v existujícím kódu. Hledejte opakující se prvky UI nebo logiku, která může být zapouzdřena.
Příklad:
<!-- Před: Komplexní formulář -->
<template>
<div>
<label for="name">Jméno:</label>
<input type="text" id="name" v-model="name">
<label for="email">E-mail:</label>
<input type="email" id="email" v-model="email">
<button @click="submitForm">Odeslat</button>
</div>
</template>
<!-- Po: Použití znovupoužitelných komponent -->
<template>
<div>
<InputField label="Jméno" v-model="name" type="text" />
<InputField label="E-mail" v-model="email" type="email" />
<SubmitButton @click="submitForm" />
</div>
</template>
2. Čisté komponenty
Zaměřujte se na komponenty, které nejen fungují, ale fungují dobře, přičemž zohledňujete čitelnost kódu, udržovatelnost a testovatelnost. Čisté komponenty se snadno chápou, upravují a ladí.
Tip: Pište komponenty, které se snadno chápou a údržují. Používejte jasné konvence pojmenování, konzistentní formátování a dobře definované odpovědnosti.
Příklad:
<!-- Špatně: Komponenta se smíšenými starostmi -->
<template>
<div>
<button @click="handleClick">{{ buttonText }}</button>
<div v-if="showDetails">{{ details }}</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const buttonText = ref('Zobrazit detaily');
const showDetails = ref(false);
const details = ref('');
async function handleClick() {
showDetails.value = !showDetails.value;
if (showDetails.value) {
details.value = await fetchData();
}
}
</script>
<!-- Dobře: Komponenta se zaměřenou odpovědností -->
<template>
<div>
<ShowDetailsButton @click="toggleDetails" :text="buttonText" />
<DetailsDisplay v-if="showDetails" :details="details" />
</div>
</template>
<script setup>
import { ref } from 'vue';
import ShowDetailsButton from './ShowDetailsButton.vue';
import DetailsDisplay from './DetailsDisplay.vue';
const showDetails = ref(false);
const details = ref('');
const buttonText = ref('Zobrazit detaily');
async function toggleDetails() {
showDetails.value = !showDetails.value;
if (showDetails.value) {
details.value = await fetchData();
}
}
</script>
3. Více komponent v jednom souboru
U malých, nezávislých komponent zvažte jejich uchovávání ve stejném souboru. To může snížit počet souborů v projektu a zlepšit rychlost vývoje, zejména pro komponenty, které jsou těsně provázány.
Tip: Vytvářejte zbytečné soubory pro jednoduché komponenty. Tento přístup používejte pro komponenty, které se používají pouze na jednom místě.
Příklad:
<template>
<div>
<MyButton @click="handleClick">Klikni na mě</MyButton>
</div>
</template>
<script setup>
import MyButton from './MyButton.vue';
function handleClick() {
alert('Tlačítko bylo kliknuto!');
}
</script>
<template>
<button @click="$emit('click')">
<slot></slot>
</button>
</template>
<script setup>
defineEmits(['click']);
</script>
4. Vzor kontrolovaných props
Tento vzor vám umožňuje přepsat interní stav komponenty z nadřazené komponenty. To je užitečné, když potřebujete vynutit stav komponenty zvenčí, například kontrolu viditelnosti modálu nebo výběru v rozevíracím seznamu.
Tip: Použijte tento vzor, když potřebujete vynutit stav komponenty zvenčí. Předejte props komponentě pro kontrolu jejího interního stavu.
Příklad:
<!-- Modal.vue -->
<template>
<div v-if="isOpen" class="modal">
<div class="modal-content">
<slot></slot>
<button @click="closeModal">Zavřít</button>
</div>
</div>
</template>
<script setup>
defineProps({
isOpen: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['close']);
function closeModal() {
emit('close');
}
</script>
<!-- Nadřazená komponenta -->
<template>
<div>
<button @click="showModal = true">Otevřít modal</button>
<Modal :isOpen="showModal" @close="showModal = false">
<p>Obsah modálu</p>
</Modal>
</div>
</template>
<script setup>
import { ref } from 'vue';
import Modal from './Modal.vue';
const showModal = ref(false);
</script>
5. Metadata komponenty
Přidejte metadata komponentám, abyste poskytli další informace jiným komponentám. To lze použít pro konfiguraci komponent, předávání dalších informací nebo usnadnění komunikace mezi komponentami.
Tip: Používejte metadata pro konfiguraci komponent nebo předávání dalších informací. To může být užitečné pro tooling nebo poskytnutí kontextu jiným komponentám.
Příklad:
<!-- Komponenta A -->
<template>
<div>Komponenta A</div>
</template>
<script>
export default {
meta: {
componentType: 'display'
}
}
</script>
<!-- Komponenta B -->
<template>
<div>Komponenta B</div>
</template>
<script>
export default {
meta: {
componentType: 'formField'
}
}
</script>
Vzory návrhu composable
1. Vzor objektu možností
Použijte objekt k předávání parametrů do composables. To umožňuje flexibilitu a škálovatelnost. Je to upřednostňovaná metoda pro předávání mnoha možností na composable.
Tip: Tento vzor se používá v VueUse a je vysoce doporučen, když potřebujete konfigurovat chování composable.
Příklad:
// useFetch.js
import { ref, onMounted } from 'vue';
export function useFetch(url, options = {}) {
const data = ref(null);
const loading = ref(false);
const error = ref(null);
const { method = 'GET', headers = {}, body = null } = options;
async function fetchData() {
loading.value = true;
try {
const response = await fetch(url, {
method,
headers,
body: body ? JSON.stringify(body) : null
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
data.value = await response.json();
} catch (e) {
error.value = e;
} finally {
loading.value = false;
}
}
onMounted(() => {
fetchData();
});
return { data, loading, error, fetchData };
}
// V komponentě:
import { useFetch } from './useFetch';
export default {
setup() {
const { data, loading, error } = useFetch('/api/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: { key: 'value' }
});
return { data, loading, error };
}
}
2. Inline Composables
Vytvářejte composables přímo v souboru komponenty, abyste se vyhnuli vytváření nových souborů. Toto je zvlášť užitečné pro composables, které jsou velmi specifické pro jednu komponentu a nejsou určeny k opětovnému použití jinde. Používejte inline composables pro malou, komponent-specifickou logiku. Tento přístup udržuje související kód dohromady a zjednodušuje strukturu komponenty.
Tip: Používejte inline composables pro malou, komponent-specifickou logiku, která nemusí být použita jinde. Tento přístup udržuje související kód pohromadě a zjednodušuje strukturu vaší komponenty.
Příklad:
Řekněme, že máte toto v komponentě:
<template>
<div>{{ formattedDate }}</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const useFormattedDate = () => {
const rawDate = ref(new Date())
const formattedDate = computed(() => {
const options = { year: 'numeric', month: 'long', day: 'numeric' }
return rawDate.value.toLocaleDateString(undefined, options)
})
return { formattedDate }
}
const { formattedDate } = useFormattedDate()
</script>
Je v pořádku zatím, ale pokud budete potřebovat toto formátování data kdekoli jinde, jste ve ztrátě. Zde je refaktorovaná verze:
// src/composables/useFormattedDate.ts
import { ref, computed } from 'vue'
export function useFormattedDate() {
const rawDate = ref(new Date())
const formattedDate = computed(() => {
const options = { year: 'numeric', month: 'long', day: 'numeric' }
return rawDate.value.toLocaleDateString(undefined, options)
})
return { formattedDate }
}
<!-- Ve vaší komponentě -->
<template>
<div>{{ formattedDate }}</div>
</template>
<script setup>
import { useFormattedDate } from './composables/useFormattedDate'
const { formattedDate } = useFormattedDate()
</script>
Nyní můžete znovupoužívat useFormattedDate v libovolné komponentě. Problém vyřešen.
3. Psaní lepších composables
Extrahujte malé kousky logiky do funkcí, které můžete snadno opakovaně používat. To podporuje opětovné použití kódu, snižuje duplikaci a činí váš kód lépe udržitelným.
Tip: Používejte composables k organizaci a opětnému použití obchodní logiky. Uvažujte o composables jako o znovupoužitelných stavebních blocích pro vaši aplikaci.
Příklad:
// useLocalStorage.ts
import { ref, watch } from 'vue';
export function useLocalStorage<T>(key: string, defaultValue: T) {
const storedValue = localStorage.getItem(key);
const value = ref<T>(storedValue !== null ? JSON.parse(storedValue) : defaultValue);
watch(
value,
(newValue) => {
localStorage.setItem(key, JSON.stringify(newValue));
},
{ deep: true }
);
return value;
}
// Použití v komponentě:
<script setup>
import { useLocalStorage } from './useLocalStorage';
const theme = useLocalStorage('theme', 'light');
</script>
4. Začněte s rozhraním
Definujte, jak bude composable používán, než ho implementujete. Toto je forma "design-first" vývoje, která vám pomůže objasnit účel, vstupy a výstupy composable, než napíšete jakýkoliv kód.
Tip: Definujte vstupy (props, možnosti) a výstupy (vrácené hodnoty) composable nejdřív. To vám pomůže zaměřit se na API composable.
Příklad:
// Před implementací: useCounter.js
// By mělo přijmout počáteční hodnotu
// Mělo by vrátit počet a metody pro zvýšení a snížení
// Implementace:
import { ref } from 'vue';
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
return { count, increment, decrement };
}
5. Znovupoužití logiky s scoped slots
Používejte scoped slots k znovupoužití logiky mezi komponentami jedinečným způsobem. Scoped slots umožňují nadřazené komponentě předat data a logiku dceřiné komponentě, čímž poskytují flexibilní způsob, jak sdílet funkcionalitu.
Tip: Scoped slots lze použít k předávání dat a logiky z nadřazené komponenty na podřízenou komponentu. To umožňuje podřízené komponentě renderovat svůj obsah na základě dat a logiky poskytnuté nadřazenou komponentou.
Příklad:
<!-- DataFetcher.vue -->
<template>
<div>
<div v-if="loading">Načítání...</div>
<div v-else-if="error">Chyba: {{ error }}</div>
<div v-else>
<slot :data="data" :loading="loading" :error="error"></slot>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const props = defineProps({
url: {
type: String,
required: true
}
});
const data = ref(null);
const loading = ref(true);
const error = ref(null);
onMounted(async () => {
try {
const response = await fetch(props.url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
data.value = await response.json();
} catch (e) {
error.value = e.message;
} finally {
loading.value = false;
}
});
</script>
<!-- Použití v komponentě -->
<DataFetcher url="/api/items">
<template v-slot="{ data, loading, error }">
<div v-if="loading">Načítání položek...</div>
<div v-else-if="error">Chyba: {{ error }}</div>
<div v-else>
<ul>
<li v-for="item in data" :key="item.id">{{ item.name }}</li>
</ul>
</div>
</template>
</DataFetcher>
Obecné tipy a osvědčené postupy
1. Ref vs. Reactive
Pochopte rozdíly mezi ref a reactive a vyberte si vhodný pro váš případ použití. ref se používá pro primitivní hodnoty, zatímco reactive se používá pro objekty a pole.
Tip: Používejte
refpro primitivní hodnoty areactivepro objekty a pole. To vám pomůže s reaktivitou Vue.
Příklad:
<script>
import { ref, reactive } from 'vue';
export default {
setup() {
const count = ref(0); // ref pro primitivní hodnotu
const user = reactive({ name: 'Jan', age: 30 }); // reactive pro objekt
return { count, user };
}
}
</script>
2. Efektivní správa stavu
Efektivně strukturujte stav ve vašich aplikacích. To je zásadní pro správu toku dat a zajištění, aby vaše aplikace zůstala spravovatelná, když roste. Zvažte vhodné knihovny pro správu stavu pro větší aplikace.
Tip: Zvažte používání Pinia nebo Vuex pro správu stavu ve větších aplikacích. Tyto knihovny poskytují centralizovanou správu stavu a usnadňují manipulaci se složitým stavem aplikace.
Příklad: (Ilustrativní - skutečná implementace závisí na zvolené knihovně správy stavu)
// Příklad Pinia (koncepční)
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
isLoggedIn: false,
user: null
}),
actions: {
login(userData) {
this.isLoggedIn = true;
this.user = userData;
},
logout() {
this.isLoggedIn = false;
this.user = null;
}
}
});
3. Používejte uvozovky ke sledování vnořených hodnot
Sledujte vnořené hodnoty přímo pomocí uvozovek. To vám umožňuje sledovat změny konkrétních vlastností v objektu, což spouští aktualizace, když se tyto vlastnosti změní.
*Tip: Používejte uvozovky v možnosti watch ke sledování vnořených vlastností objektu. To je efektivnější a specifičtější způsob, jak monitorovat změny.
Příklad:
<script>
import { ref, watch } from 'vue';
export default {
setup() {
const data = ref({ user: { name: 'Jan' } });
watch(() => data.value.user.name, (newName) => {
console.log('Jméno se změnilo:', newName);
});
return { data };
}
}
</script>
4. Vzor extrahování podmíněného
Rozdělujte komponenty na základě podmíněné logiky. To zlepšuje čitelnost a udržovatelnost oddělením obav. Činí komponenty snadněji srozumitelnými a testovatelný.
Tip: Pokud má komponenta složitou podmíněnou logiku, zvažte její rozdělení do menších komponent. To vytváří více zaměřené a spravovatelné komponenty.
Příklad:
<!-- Před: Komplexní podmínka -->
<template>
<div>
<div v-if="isLoading">Načítání...</div>
<div v-else-if="error">Chyba: {{ error }}</div>
<div v-else>
<UserList v-if="users.length > 0" :users="users" />
<NoUsersMessage v-else />
</div>
</div>
</template>
<!-- Po: Použití extrahovaných komponent -->
<template>
<LoadingIndicator v-if="isLoading" />
<ErrorMessage v-if="error" :message="error" />
<UsersDisplay v-else :users="users" />
</template>
5. 6 důvodů pro rozdělení komponent
Rozdělujte komponenty na menší kousky, aby se zlepšila organizace kódu a znovupoužitelnost. To zvyšuje čitelnost, udržovatelnost a testovatelnost vašeho kódu.
Tip: Menší komponenty jsou snadněji srozumitelné, testovatelné a udržitelné. Také podporují opětovné použití, protože je můžete používat v několika částech vaší aplikace.
Příklad:
<!-- Před: Monolitická komponenta -->
<template>
<div>
<Header />
<Sidebar />
<MainContent />
<Footer />
</div>
</template>
<!-- Po: Použití extrahovaných komponent -->
<template>
<AppLayout>
<template v-slot:header><Header /></template>
<template v-slot:sidebar><Sidebar /></template>
<template v-slot:main><MainContent /></template>
<template v-slot:footer><Footer /></template>
</AppLayout>
</template>
6. Nepřepisujte CSS komponenty
Vyvarujte se přímé modifikace CSS komponenty zvnějšku komponenty. To může vést k neočekávanému chování a usnadňuje údržbu stylů vaší aplikace. Inkapsulujte styly v samotné komponentě.
Tip: Používejte props nebo slots k přizpůsobení vzhledu komponenty. To umožňuje kontrolované stylizování a zabraňuje neočekávaným konfliktům stylů.
Příklad:
<!-- Button.vue -->
<template>
<button :class="['button', variant]" @click="emit('click')">
<slot></slot>
</button>
</template>
<script setup>
const props = defineProps({
variant: {
type: String,
default: 'primary'
}
});
const emit = defineEmits(['click']);
</script>
<style scoped>
.button {
/* Základní styly tlačítka */
}
.primary {
/* Primární styly tlačítka */
}
.secondary {
/* Sekundární styly tlačítka */
}
</style>

