Framework entegrasyonları · Vue + Three
Yükleme & onBeforeUnmount: Composable Kargo ve Dispose Borusu
Doku veya model ağdan gelirken bileşen ömrü biterse
yarış ve sızıntı üretir. Bu sayfa Vue + Three'de
composable asenkron yükleme ile onBeforeUnmount dispose
hattını anlatır: Suspense merkezi değil · Three varlık ↔ bileşen ömrü.
Holodepth bu yaprağı Vue 101 veya R3F Suspense turunun kopyası olarak yazmaz. R3F asenkron köprü Yükleme & Suspense köprüsü yaprağındadır (useLoader kimliği, askı merkezi). Angular ömür pasaportu Yükleme & OnDestroy yaprağındadır (ngOnDestroy registry, RxJS iptal). Mount kökü Mount & kök sözleşmesi , kare metronom Kare döngüsü & watchEffect yaprağındadır. glTF şema format izleklerindedir; sıkıştırma Platform · Asset yaprağındadır.
Vue + Three yolculuğunun omurgası şu zincirde okunur: mount → harita → tik → kargo → bağ → kaçış. Bu sayfa kargo kapısıdır: asenkron varlık nerede yüklenir, unmount'ta GPU ne zaman salınır, KeepAlive neden «görünmez ama canlı» tuzağıdır? Önceki yaprak rAF stop siluetini mühürledi; burada geometry, material ve texture dispose derinliği yazılır.
Sayfayı bitirdiğinizde şu cümleyi kurabilmelisiniz: «Yük composable ömrüne bağlıdır. AbortController unmount'ta iptal eder. onBeforeUnmount dispose borusunu sırayla çalıştırır. KeepAlive pause eder ama GPU'yu öldürmez. Route ayrılışında yarış guard'ı zorunludur. URL allowlist dışına çıkılmaz. Vue Suspense isteğe bağlı köprüdür, merkez değildir. Sonraki kapı olay bağları ve picking'tir.»
Bu sayfanın sınırı · komşu konularla ayrım
Bu sayfa Vue + Three'de composable yükleme / onBeforeUnmount sahipliğini anlatır:
useAsyncAssetbenzeri composable boruAbortController· route leave yarışı- Dispose sırası: geometry → material → texture
- KeepAlive önbellek tuzağı (pause ama GPU canlı)
- URL allowlist · sınırsız kullanıcı fetch'i yok
- Vue
<Suspense>yalnızca isteğe bağlı köprü
Bilinçli olarak dışarıda bırakılan konular:
- R3F useLoader / Suspense merkezi → R3F · Yükleme & Suspense
- Angular ngOnDestroy / takeUntilDestroyed → Angular · Yükleme & OnDestroy
- Mount / provide kökü → Mount & kök sözleşmesi
- rAF / watchEffect → Kare döngüsü & watchEffect
- Pointer / picking → Olay bağları & picking
- Reaktif kaçış → Reaktif kaçış & performans
- Vue temeli / SFC 101 · glTF şema · Draco derinliği
Kısa ayrım: Bu sayfa «composable kargo ömürle nasıl mühürlenir?» · R3F yükleme «Suspense askı kimliği» · Angular yükleme «DestroyRef iptal pasaportu» · Format «paket içinde ne var?».
Dispose sırası: geometry, material, texture
Three kaynakları Vue reaktivitesinin dışında GPU'da yaşar. Holodepth dispose
borusunda sıra bilinçlidir: önce sahne grafından
remove, ardından mesh'e özel
geometry.dispose(), sonra materyal(ler) ve bağlı
texture.dispose() çağrıları. Ters sıra veya atlama sessiz
sızıntı üretir; DevTools bellek grafiği yavaş yükselir, sekme kapanınca
düzelmez.
MeshStandardMaterial gibi çoklu doku taşıyan materyallerde
map, normalMap, roughnessMap gibi
alanlar ayrı ayrı kontrol edilir. Paylaşılan doku cache'den geliyorsa dispose
etmezsiniz; yerel kopyaysa salarsınız. Holodepth kuralı: dispose kararı
sahiplik modeline bağlıdır, «her unmount'ta her şeyi öldür» değildir.
Renderer ve WebGL context mount köküne aittir; parça bileşen
renderer.dispose() çağırmaz. Child yalnızca kendi yüklediği
geometri/materyal/dokuyu borudan geçirir. Bu ayrım Angular host registry
ve R3F Fiber unmount semantiğiyle aynı diplomatiktir; Vue'da boru
onBeforeUnmount içinde toplanır.
Yanlış dispose sırası, görünmez ama kalıcı GPU faturasıdır.
Composable asenkron yükleme
Holodepth Vue + Three varsayılanı: yükleme mantığını
useAsyncAsset benzeri bir composable'da toplamak. Böylece
fetch/loader başlangıcı, iptal jetonu, hata bayrağı ve dispose listesi tek
yerde mühürlenir. Dağınık onMounted içi
TextureLoader().load çağrıları ikinci boru riski taşır;
composable «tek sahip» sözleşmesidir.
Composable dönüşü tipik olarak shallowRef ile işaretlenmiş
Object3D veya Texture tutar; reaktif Proxy sahne grafına sızmaz (önceki
harita yaprağı). Yükleme durumu için ayrı ref
(idle / loading / ready /
error) şablon fallback'ine köprü olur; bu, R3F Suspense
merkezinin yerine geçen Vue diplomasisidir.
Imperatif Three'de Promise çözülünce scene.add edersiniz.
Vue'da aynı iş composable içinde yapılır; şablon yalnızca «hazır mı?»
sorusunu sorar. TresJS gibi kütüphaneler benzer yüzeyi deklaratif loader
prop'larıyla sunabilir; Holodepth kütüphane katalogu açmaz, yalnızca
«yükleme composable'da yaşar» ilkesini mühürler.
KeepAlive: pause ama GPU canlı
<KeepAlive> bileşeni DOM ağacında deaktive eder;
onDeactivated çalışır, fakat onBeforeUnmount
çalışmaz. Three geometri, materyal ve dokuları GPU'da yaşamaya devam
eder. Holodepth: KeepAlive ile sarılmış 3D panel «görünmez ama faturalı»
tuzağıdır. Sekme değişince Vue pause eder; bellek ve VRAM düşmez.
İki politika vardır. Önbellek bilinçli: deactivate'te yalnızca rAF durdurulur (önceki kare yaprağı), dispose yapılmaz; activate'te döngü devam eder. VRAM tasarrufu: deactivate'te ağır dokuları serbest bırakıp activate'te yeniden yükleme kabul edilir. Ürün cümlesi tek satır olmalı: KeepAlive cache mi, VRAM bütçesi mi?
onActivated / onDeactivated hook'ları dispose
borusunun yerine geçmez; yalnızca pause/resume ve isteğe bağlı hafifletme
için kullanılır. Unmount gerçekten geldiğinde
onBeforeUnmount tam boruyu çalıştırmalıdır. Angular'da benzer
«route reuse strategy» tuzağı vardır; Vue'da KeepAlive daha görünür bir
yüzey sunar.
AbortController: iptal jetonu
Fetch tabanlı yüklemede AbortController standart iptal
mekanizmasıdır. Composable her yük turunda yeni bir controller üretir;
onBeforeUnmount veya prop değişiminde önceki controller
abort() edilir. Çözüm geldiğinde sinyal düşmüşse sahneye
dokunmayın; gelen buffer veya texture'ı hemen dispose edin veya hiç kabul
etmeyin.
Three TextureLoader ve model loader'ları doğrudan AbortSignal
almayabilir; Holodepth pratikte iki katman kullanır: fetch katmanında
abort, loader katmanında «jeton bayrağı» guard. Guard false ise callback
içindeki scene.add çalışmaz. Angular'daki
takeUntilDestroyed ve R3F'in ağaç unmount semantiği aynı
sorunu farklı API ile çözer; Vue'da AbortController + bayrak ikilisi
yaygındır.
Aynı composable içinde üst üste iki yükleme başlatılırsa eski akış iptal
edilir. Bu, Angular switchMap ruhunun Vue karşılığıdır.
İptal edilmiş akışın hata callback'i sessizce yutulmamalı; en azından
geliştirici logu veya metrik üretilmelidir.
Route ayrılışı: unmount yükü geçer
Vue Router ile hızlı gezinmede klasik hastalık: Promise çözülünce ölü
bileşene parent.add(mesh) veya reaktif ref yazımı
yapılır. Holodepth: route leave guard'ı veya composable içi «canlı mı?»
bayrağı zorunludur. onBeforeRouteLeave yalnızca navigasyon
onayı değildir; 3D panelde abort + dispose ön hazırlığıdır.
KeepAlive ile birleşince yarış daha sinsidir: route değişir, bileşen
deactivate olur, yükleme arka planda biter ve activate olmayan önbelleğe
yazar. Guard hem unmount hem deactivate yolunu kapsamalıdır. Nuxt
onBeforeRouteLeave ve sayfa meta'sı aynı diplomatiktir;
kurulum detayı framework rehberindedir, burada ömür kuralı yazılır.
Ölü rotanın canlı dokusu, sessiz sahne kirliliğidir.
onBeforeUnmount dispose borusu
Dağınık dispose() çağrıları unutulur. Holodepth pratikte bir
kaynak listesi veya disposeSceneGraph(root) yardımcısı
tutar. onBeforeUnmount sırası tipik olarak şöyledir: rAF
stop (kare yaprağı), abort controller, sahne grafından remove, geometry
dispose, material dispose, texture dispose, listener temizliği. Renderer
kök mount'a aittir; child dokunmaz.
| Model | Kim dispose eder? | Ne zaman? |
|---|---|---|
| Bileşen yerel | Yükleyen composable | onBeforeUnmount |
| Pinia / servis cache | Uygulama / bilinçli purge | Ömür boyu veya manuel |
| KeepAlive önbellek | Deactivate politikasına göre | Activate veya gerçek unmount |
Ortak materyali ilk çocuğun unmount'unda dispose etmek ikinci çocuğu
öldürür. Cache modelinde dispose etmeyin; referans sayacı veya «misafir»
sözleşmesi yazın. Vue provide ile paylaşılan loader cache'i
mount yaprağındaki kök sözleşmeyle uyumlu olmalıdır.
URL allowlist: sınırsız fetch yok
Kullanıcı girdisini doğrudan loader URL'sine bağlamak SSRF ve bellek taşması riski taşır. Holodepth: panel asset'leri sabit allowlist veya sunucu tarafı çözümlenmiş kimlikle gelir. Dinamik seçimde önce anahtar doğrulanır, anahtar allowlist'ten gerçek path üretilir; ham string asla fetch'e gitmez.
Query ile cache busting bilinçli ürün kararıdır; rastgele query her
mount'ta yeni indirme üretir. Versiyon hash'i build zamanında mühürlenir.
Vue import.meta.url veya Vite asset import'u geliştirme
kolaylığıdır; üretimde CDN path ve allowlist politikası ayrı yazılır.
Çoklu eşzamanlı büyük model indirmesi ağ ve decode bütçesini yer. Ürün tarafında eşzamanlılık tavanı ve boyut tavanı (platform asset yaprağı) composable ile birleştirilir; burada yalnızca «kullanıcı URL'si serbest değildir» kuralı mühürlenir.
Vue Suspense: isteğe bağlı köprü
Vue 3 <Suspense> asenkron setup ve async component
için fallback sunar. Holodepth Vue + Three'de bunu merkez
yapmaz: R3F track kimliği Suspense etrafındadır; Vue track'te composable
boru ve durum ref'leri yeterlidir. Yine de ürün isterse
async SFC'yi Suspense ile sarmak mümkündür; fallback iskelet veya düşük
poli proxy mesh olmalıdır, boş siyah kutu değil.
Suspense sınırı tüm 3D paneli kapsarsa kaba ama basittir; parça parça sınır progressive reveal verir, layout zıplaması riski taşır. KeepAlive ile Suspense birlikte kullanıldığında deactivate yolunda fallback tekrar gösterilmez; önbellek politikası ayrı yazılır. Bu bölüm köprü notudur; derin Suspense müfredatı R3F yaprağına ve Vue resmi rehberine bırakılır.
Hata yüzeyi ve yarım yük
404, CORS, bozuk buffer veya abort sonrası sessiz siyah sahne üretebilir.
Composable error ref'i şablona veya toast katmanına bağlanır;
reject yutulmaz. Yarım yük (geometry geldi, texture gelmedi) durumunda
kısmi kaynaklar borudan geri alınır; «yarım mesh» sahneye eklenmez.
Abort ile gerçek hata ayrımı önemlidir: abort kullanıcı/gezinme
politikasıdır, hata değildir. UI'de abort için spinner sonsuza uzamaz;
yeni rota veya yeni prop ile durum sıfırlanır. R3F Error Boundary ve
Angular catchError kopyası değildir; Vue'da composable
durum makinesi ürün standardıdır.
Kare yaprağı tiki ince tutmayı mühürledi. Fetch, parse, glTF decode ve
texture upload'u her kareye yaymak jank üretir. Yük composable Promise
yolunda bitsin; yerleşince demand veya sürekli loop tek kare boyasın.
Ağır decode'u requestIdleCallback veya worker'a itmek ürün
kararıdır; yükleme şeridi ile kare şeridi ayrı metronomdur.
Asenkron kargo tamamlanmadan rAF sahneye yazmamalıdır.
Güvenli ömür: simüle composable
Örnekler öğretim amaçlıdır; paket sürümünüzü doğrulayın. Sabit allowlist URL kullanın; kullanıcı string'i loader'a gitmez. Vue reaktivite temeli öğretilmez.
Simüle · useAsyncAsset: AbortController + guard + shallowRef. Mount'ta parent Object3D hazır kabul edilir.
import {
onBeforeUnmount, shallowRef, ref, watch, type Ref, type ShallowRef,
} from 'vue'
import * as THREE from 'three'
const PANEL_TEXTURE = '/assets/demo/panel-albedo.jpg' // allowlist · kullanıcı string'i yok
type LoadStatus = 'idle' | 'loading' | 'ready' | 'error'
export function useAsyncTexture(
parent: ShallowRef<THREE.Object3D | null>,
url: string = PANEL_TEXTURE,
) {
const status = ref<LoadStatus>('idle')
const texture = shallowRef<THREE.Texture | null>(null)
let controller: AbortController | null = null
let alive = true
const load = (nextUrl: string) => {
controller?.abort()
controller = new AbortController()
const signal = controller.signal
status.value = 'loading'
fetch(nextUrl, { signal })
.then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.blob()
})
.then((blob) => {
if (!alive || signal.aborted) return
const objectUrl = URL.createObjectURL(blob)
new THREE.TextureLoader().load(
objectUrl,
(tex) => {
URL.revokeObjectURL(objectUrl)
if (!alive || signal.aborted) {
tex.dispose()
return
}
tex.colorSpace = THREE.SRGBColorSpace
texture.value = tex
status.value = 'ready'
},
undefined,
() => {
URL.revokeObjectURL(objectUrl)
if (alive && !signal.aborted) status.value = 'error'
},
)
})
.catch((err) => {
if (err?.name === 'AbortError') return
if (alive) status.value = 'error'
})
}
watch(
() => parent.value,
(root) => {
if (!root) return
load(url)
},
{ immediate: true },
)
onBeforeUnmount(() => {
alive = false
controller?.abort()
texture.value?.dispose()
texture.value = null
})
return { status, texture }
}
Simüle · dispose borusu: sıralı geometry / material / texture. Paylaşılan cache dokusuna dokunulmaz; yerel mesh tam salınır.
import { onBeforeUnmount, type ShallowRef } from 'vue'
import * as THREE from 'three'
const TEXTURE_KEYS = ['map', 'normalMap', 'roughnessMap', 'metalnessMap', 'aoMap'] as const
function disposeMaterial(material: THREE.Material, ownedTextures: Set<THREE.Texture>) {
if ('dispose' in material && typeof material.dispose === 'function') {
material.dispose()
}
if (material instanceof THREE.MeshStandardMaterial) {
for (const key of TEXTURE_KEYS) {
const tex = material[key]
if (tex && ownedTextures.has(tex)) tex.dispose()
}
}
}
export function useMeshLifecycle(
parent: ShallowRef<THREE.Object3D | null>,
mesh: ShallowRef<THREE.Mesh | null>,
ownedTextures: Set<THREE.Texture> = new Set(),
) {
onBeforeUnmount(() => {
const root = parent.value
const m = mesh.value
if (!m) return
root?.remove(m)
const geom = m.geometry
if (geom) geom.dispose()
const mat = m.material
if (Array.isArray(mat)) {
mat.forEach((item) => disposeMaterial(item, ownedTextures))
} else if (mat) {
disposeMaterial(mat, ownedTextures)
}
mesh.value = null
})
}
/**
* PanelPanel.vue içinde:
* const mesh = shallowRef<THREE.Mesh | null>(null)
* useMeshLifecycle(sceneRoot, mesh, ownedSet)
* Renderer dispose mount kökünde · child çağırmaz.
*/
Güvenlik: kullanıcı girdisini URL olarak kullanmayın. Abort sonrası callback'te guard kontrolü yapın. KeepAlive deactivate'te dispose politikasını bilinçli seçin; varsayılan «hiç dispose etme» VRAM yer.
Yükleme / ömür tuzakları
- Unmount'sız yükleme: Promise ölü parent'a yazar.
- KeepAlive = dispose oldu sanmak: GPU canlı kalır.
- Paylaşılan dokuyu dispose: cache'i kırar.
- Kullanıcı URL'si: sınırsız fetch ve SSRF.
- Abort'sız fetch: route yarışı kazanır.
- Dispose sırası karışık: texture önce, geometry sonra.
- Renderer child'da dispose: tüm panel ölür.
- Suspense merkez sanmak: R3F kimliği Vue'ya taşınır.
- Decode her karede: jank ve frame drop.
- Proxy'li Texture ref: markRaw / shallowRef atlanmış.
Üç yükleme profili
-
01 · Kargo
Composable yerel
- Yükleme
- useAsyncAsset · allowlist
- Ömür
- onBeforeUnmount tam dispose
- Risk
- Abort guard unutulması
-
02 · Kargo
KeepAlive önbellek
- Deactivate
- rAF pause · dispose yok
- Unmount
- Tam boru çalışır
- Risk
- Görünmez VRAM
-
03 · Kargo
Servis cache + clone
- Okuma
- Paylaşılan GLTF kökü
- Yazma
- Scene.clone sahneye
- Risk
- Cache'i child dispose eder
Holodepth perspektifleri ve sonraki kapı
Composable boruyu tek sahip yapın
Yükleme, iptal ve dispose aynı composable'da mühürlenir; dağınık onMounted callback'leri ikinci boru üretir.
Abort her turda yenilenir
Prop veya route değişiminde eski fetch ölür; guard olmadan sahneye yazım yasaktır.
KeepAlive GPU'yu öldürmez
Deactivate pause politikasıdır; VRAM politikası ayrı yazılır. Gerçek unmount'ta tam boru çalışır.
Teşhis maddeleri
- Composable abort + alive guard var mı?
- onBeforeUnmount dispose sırası doğru mu?
- KeepAlive deactivate'te ne oluyor?
- URL allowlist dışına çıkılıyor mu?
- Paylaşılan cache yanlışlıkla dispose ediliyor mu?
- Renderer child'da dispose ediliyor mu?
- Decode rAF içine gömülü mü?
- Suspense merkez sanılıp composable atlanmış mı?
Holodepth içgörüsü
Vue + Three'de «bellek sızıntısı» çoğu zaman reaktif graf değil, ölü bileşene biten Promise ve KeepAlive'da unutulan GPU'dur. Önce composable boruyu okuyun, sonra şablon fallback'ini yazın.
Sonraki kapı: olay bağları & picking
Kargo sahneye yerleşince etkileşim başlar. Sonraki sayfa Olay bağları & picking pointer olayları, raycast ve reaktif seçim diplomasisini anlatır. Reaktif kaçış performans yaprağında derinleşir.