Bilmeye değer yedi ileri seviye Vue 3 tekniği
Composable'lar, Teleport, Suspense, özel yönergeler, render fonksiyonları, provide/inject ve eklentiler; her biri koduyla birlikte.
Vue 3 bir süredir gündelik frontend işinin parçası. Sezgisel, üzerinde çalışması keyifli ve temel bilgiler seni epey ileri götürüyor. Aşağıdakiler o noktadan sonrası: kendi projelerimde işime yaramış yedi teknik.
1. setup() sonrasında Composition API
Composition API'nin artık ileri seviye sayılmadığını söyleyebilirsin, haklısın da. Çoğumuz setup() ve tepkisel referanslarla rahatız. Genelde kullanılmadan kalan kısım, mantığı composable'lara çıkarmak.
Composable'ı, durum taşıyan mantığı kapsayan bir fonksiyon olarak düşün. Bileşenler yalnızca render işiyle uğraştığı için inceliyor, mantık seçeneklere göre değil özelliğe göre gruplanıyor ve aynı mantık, mixin'lerin eskiden çıkardığı sorunlar olmadan paylaşılabiliyor.
Diyelim ki veri çeken, yükleniyor ve hata durumlarını izleyen bir özelliğin var. Bunu her bileşende tekrarlamak yerine:
// composables/useFetch.js
import { ref, onMounted } from "vue";
export function useFetch(url) {
const data = ref(null);
const error = ref(null);
const loading = ref(true);
async function fetchData() {
loading.value = true;
error.value = null;
try {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
data.value = await res.json();
} catch (e) {
error.value = e;
} finally {
loading.value = false;
}
}
onMounted(fetchData);
return { data, error, loading, fetchData };
}
Sonra herhangi bir bileşen bunu kullanabiliyor:
<template>
<div>
<div v-if="loading">Loading posts...</div>
<div v-else-if="error">Error: {{ error.message }}</div>
<ul v-else>
<li v-for="post in data" :key="post.id">{{ post.title }}</li>
</ul>
</div>
</template>
<script setup>
import { useFetch } from "@/composables/useFetch";
const { data, error, loading } = useFetch(
"https://jsonplaceholder.typicode.com/posts"
);
</script>
2. Teleport: kaçması gereken öğeler için
Derin bir bileşenin içinden modal, ipucu kutusu ya da bildirim render etmeye çalışırken z-index savaşları verdiysen cevap Teleport. Şablonunun bir parçasını, bileşen hiyerarşisinin dışında, DOM'un başka bir yerinde render ediyor; bu sırada bileşeninin durumuyla tepkiselliği koruyor.
Modallara ve diyaloglara, bildirimlere, ipucu kutularına ve açılır menülere uyuyor.
<!-- In your App.vue or main layout file -->
<body>
<div id="app"></div>
<div id="modals-container"></div>
<!-- This is our teleport target -->
</body>
<!-- components/MyModal.vue -->
<template>
<Teleport to="#modals-container">
<div v-if="isOpen" class="modal-backdrop" @click="close">
<div class="modal-content" @click.stop>
<h3>My Awesome Modal</h3>
<p>This modal content is rendered outside the component!</p>
<button @click="close">Close</button>
</div>
</div>
</Teleport>
</template>
<script setup>
import { ref } from "vue";
const isOpen = ref(false);
const open = () => (isOpen.value = true);
const close = () => (isOpen.value = false);
// Expose open/close for parent component to use
defineExpose({ open, close });
</script>
<style scoped>
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 8px;
min-width: 300px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
</style>
MyModal nerede kullanılırsa kullanılsın içeriği #modals-container içine düşüyor; bu da stil çakışmalarını denklemden çıkarıyor.
3. Asenkron yükleme durumları için Suspense
Veriyi ya da asenkron yüklenen bir bileşeni beklemek arayüzü ağır hissettiriyor. Suspense, bileşen ağacındaki asenkron bağımlılıkları koordine ediyor ve onlar çözülene kadar bir yedek içerik gösteriyor; böylece kullanıcı boş bir alan ya da bozuk bir düzen yerine yükleniyor durumu görüyor.
Mount anında veri çeken bir bileşen:
<!-- components/AsyncDataDisplay.vue -->
<template>
<div>
<h2>Data from API:</h2>
<p>{{ data.title }}</p>
</div>
</template>
<script setup>
import { ref } from "vue";
const data = ref(null);
// Simulate an async data fetch
await new Promise((resolve) => setTimeout(resolve, 2000));
const res = await fetch("https://jsonplaceholder.typicode.com/todos/1");
data.value = await res.json();
</script>
Suspense ile sarıldığında:
<template>
<div>
<h1>My App</h1>
<Suspense>
<!-- Main content that might be async -->
<AsyncDataDisplay />
<!-- Fallback content while async components are loading -->
<template #fallback>
<div>Loading awesome data...</div>
</template>
</Suspense>
</div>
</template>
<script setup>
import { defineAsyncComponent } from "vue";
// Define an async component
const AsyncDataDisplay = defineAsyncComponent(
() => import("./components/AsyncDataDisplay.vue")
);
</script>
AsyncDataDisplay setup bloğundaki await'i çözerken yedek içerik görünüyor. İş bitince yerini gerçek içerik alıyor.
4. DOM işleri için özel yönergeler
Bazen doğru araç bir bileşen değil. Düşük seviyeli DOM müdahalesi ya da belirli bir bileşene bağlı olmayan yeniden kullanılabilir davranış için özel yönergeler, bağlandıkları öğeye doğrudan erişim veriyor. v-focus, v-tooltip ya da v-lazy-load gibi.
// main.js or a dedicated directives file
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
app.directive("focus", {
mounted(el) {
el.focus();
},
});
app.mount("#app");
Kullanımı şöyle:
<template>
<input v-focus type="text" placeholder="I will be focused on mount" />
</template>
Bileşen mount edildiğinde input odaklanıyor. Yönergeler created, mounted, updated ve unmounted kancalarına takılarak öğenin yaşam döngüsü boyunca davranışı yönetebiliyor.
5. Render fonksiyonları ve JSX
Neredeyse her şey için doğru seçim Vue'nun şablon söz dizimi: bildirimsel, okunabilir ve verimli derleniyor. Render fonksiyonları ve JSX, render üzerinde programatik denetime ihtiyaç duyduğun ender durumlar için: çok dinamik bir tablo üreticisi ya da yapısı karmaşık prop'lara göre değişen bir kütüphane bileşeni gibi.
Yapı çok değişmiyorsa, maksimum esneklik gerektiren bir arayüz kütüphanesi yazmıyorsan ya da bileşenleri tamamen JavaScript veya TypeScript'te yazmayı tercih etmiyorsan buna muhtemelen uzanmayacaksın.
// components/DynamicHeading.vue
import { h } from "vue";
export default {
props: {
level: {
type: Number,
required: true,
validator: (val) => val >= 1 && val <= 6,
},
},
setup(props) {
return () => h(`h${props.level}`, `This is a level ${props.level} heading`);
},
};
Vue JSX için Babel kurulumu gerektiren JSX hâli:
// components/DynamicHeadingJSX.vue
export default {
props: {
level: {
type: Number,
required: true,
validator: (val) => val >= 1 && val <= 6,
},
},
setup(props) {
const HeadingTag = `h${props.level}`;
return () => (
<HeadingTag>This is a level {props.level} heading (with JSX)</HeadingTag>
);
},
};
6. Provide ve inject
Gerçekten global durum için cevap Pinia. Provide ve inject aradaki durum için: her seviyeden prop geçirmeden bileşen ağacının derinine ulaşması gereken veri ya da yardımcılar.
Tema bilgisine, kullanıcı tercihlerine, yardımcı fonksiyonlara ve nadiren değişen ama bütün bir alt ağacı ilgilendiren verilere uyuyor.
<!-- components/GrandparentComponent.vue -->
<template>
<div>
<h1>Grandparent</h1>
<button @click="toggleTheme">Toggle Theme</button>
<ChildComponent />
</div>
</template>
<script setup>
import { provide, ref } from "vue";
import ChildComponent from "./ChildComponent.vue";
const theme = ref("light");
const toggleTheme = () => {
theme.value = theme.value === "light" ? "dark" : "light";
};
provide("appTheme", theme); // Provide the reactive theme
provide("toggleThemeFunction", toggleTheme); // Provide a function too!
</script>
<!-- components/DeeplyNestedComponent.vue -->
<template>
<div :class="['card', appTheme === 'dark' ? 'dark-mode' : '']">
<p>Current theme: {{ appTheme }}</p>
<button @click="toggleThemeFunction">Toggle Theme (from deep)</button>
</div>
</template>
<script setup>
import { inject } from "vue";
const appTheme = inject("appTheme");
const toggleThemeFunction = inject("toggleThemeFunction"); // Inject the function
</script>
<style scoped>
.card {
border: 1px solid #ccc;
padding: 15px;
margin-top: 10px;
border-radius: 5px;
}
.dark-mode {
background-color: #333;
color: #eee;
border-color: #555;
}
</style>
DeeplyNestedComponent, GrandparentComponent hiçbir şeyi ChildComponent üzerinden geçirmeden hem temayı hem fonksiyonu alıyor.
7. Global özellikler ve eklentiler
Bazen bir şeyin bütün uygulamada erişilebilir olması gerekiyor: bir HTTP örneği, bir çeviri fonksiyonu, ortak bir yardımcı.
En basit yol app.config.globalProperties. Options API'de this'e takılıyor, Composition API'de getCurrentInstance() üzerinden erişiliyor.
// main.js
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
// Attach a global property
app.config.globalProperties.$myGlobalUtil = {
sayHello: () => console.log("Hello from global util!"),
version: "1.0.0",
};
app.mount("#app");
Şablonda karşılığı <button @click="$myGlobalUtil.sayHello()">Say Hello</button>, script setup bloğunda ise:
<script setup>
import { getCurrentInstance } from "vue";
const { proxy } = getCurrentInstance();
proxy.$myGlobalUtil.sayHello();
console.log(proxy.$myGlobalUtil.version);
</script>
Birden fazla global özellik, yönerge, bileşen ya da yönlendirme mantığı içeren daha büyük işler için bir eklenti yaz. Eklenti, app örneğini ve isteğe bağlı bir seçenekler nesnesini alan bir fonksiyon.
// plugins/myPlugin.js
export default {
install: (app, options) => {
// Make a global method available
app.config.globalProperties.$translate = (key) => {
return key
.split(".")
.reduce((o, i) => (o ? o[i] : undefined), options.translations);
};
// Provide a global composable
app.provide("globalStore", {
user: "John Doe",
settings: { theme: "dark" },
});
// Or register a global component
// app.component('MyGlobalComponent', MyGlobalComponent)
},
};
// main.js
import { createApp } from "vue";
import App from "./App.vue";
import MyPlugin from "./plugins/myPlugin";
const app = createApp(App);
const translations = {
hello: "Bonjour",
welcome: {
message: "Bienvenue!",
},
};
app.use(MyPlugin, { translations }); // Use the plugin with options
app.mount("#app");
Artık herhangi bir bileşen $translate('welcome.message') çağırabiliyor ya da globalStore'u inject edebiliyor. Vue Router ve Pinia da uygulamaya aynı mekanizmayla giriyor.
Bu yedi örüntü, temellerin yetmemeye başladığı durumların çoğunu kapsıyor. Ne kadar çok uzanırsan o kadar doğal geliyorlar.