- Global vs Local Components
- Scoped Styles
- Introducing slots
- Named Slots
- Slot Styles and Compilation
- More on Slots
- Scoped slots
- Dynamic Components
- Keeping Dynamic Components Alive
- Teleporting Elements
- Working with fragments
- Vue Style Guide
Global components are registered to the main app object.
main.js
const app = createApp(App); // main application object
// these are global components
app.component('the-header', TheHeader);
app.component('base-badge', BaseBadge);
app.component('badge-list', BadgeList);
app.component('user-info', UserInfo);
Global components are used anywhere in your vue app - ie in any template.
- Local components are expressed in the component config object.
Components property is an object which has a key defines our custom HTML element and a value which is the imported component config object. So TheHeader for example, the tag for that component should be the-header.
<script>
import TheHeader from './components/TheHeader.vue';
import BadgeList from './components/BadgeList.vue';
import UserInfo from './components/UserInfo.vue';
export default {
components: {
TheHeader, /* or 'the-header': TheHeader */
BadgeList,
UserInfo
},
/* ... */
}
Note:
No matter where you added your styling, it would always be treated as global styling that affects the entire app unless it is defined with scoped attribute.
(tor:Vue component'imizde style etiketine scoped attribute eklemezsek stiller global olarak kabul eder. Her yerde geçerli olur.)
<style scoped>
section {
margin: 2rem auto;
max-width: 30rem;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
padding: 1rem;
}
/* ... */
</style>
They allow us to receive HTML content which also may be using Vue features from outside of the component.
Basically just like props but where props are meant to be used for data which a component needs, slots are meant to be used for HTML code.
UserInfo.vue
<base-card>
<!-- below base-card component content -->
<header>
<h3>{{ fullName }}</h3>
<base-badge :type="role" :caption="role.toUpperCase()"></base-badge>
</header>
<p>{{ infoText }}</p>
</base-card>
BaseCard.vue
<template>
<div class="bg-red-300">
<slot></slot>
</div>
</template>
<script>
export default {
/* ... */
}
</script>
You can give name to slots. In the component below, we defined two slots. one of them is named with 'header'
- Definition of named slots
BaseCard.vue
<template>
<div>
<header>
<!-- first slot -->
<slot name="header"></slot>
</header>
<!-- second slot -->
<slot></slot>
</div>
</template>
<script>
export default {};
</script>
- Usage of named slots
v-slot:[slotName]
.
BadgeList.vue
<base-card>
<!-- header slot content -->
<template v-slot:header>
<h2>Available Badges</h2>
</template>
<!-- v-slot:default is optional -->
<template v-slot:default>
<ul>
<li>
<base-badge type="admin" caption="ADMIN"></base-badge>
</li>
<li>
<base-badge type="author" caption="AUTHOR"></base-badge>
</li>
</ul>
</template>
</base-card>
Vue.js will analyze, compile and evaluate this template before it sends to content to the other component.So therefore, here we have access to whatever is defined inside of UserInfo, and to styling defined here also affects this markup, but not the markup of any component we might be sending our content to (that is, styles are not valid for slot contents.).
(tor:Yani header slot alanının style tanımları, baseCard component'inde (child component'de) yapılmalı. Parent comp'de geçerli degil.)
UserInfo.vue
<template>
<section>
<base-card>
<template v-slot:header>
<h3>{{ fullName }}</h3>
<base-badge :type="role" :caption="role.toUpperCase()"></base-badge>
</template>
<template v-slot:default>
<p>{{ infoText }}</p>
</template>
</base-card>
</section>
</template>
<script>
export default {
props: ['fullName', 'infoText', 'role'],
};
</script>
BaseCard.vue
<template>
<div>
<header>
<slot name="header"></slot>
</header>
<slot></slot>
</div>
</template>
<script>
export default {};
</script>
<style scoped>
header {
display: flex;
justify-content: space-between;
align-items: center;
}
div {
margin: 2rem auto;
max-width: 30rem;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
padding: 1rem;
}
</style>
BaseCard componentini header slotu olmadan kullandığımızda sayfa kaynağına (source) header elementi ekler. Örnek aşağıdaki şekilde kullanırsak.
BaseCard.vue
<template>
<div>
<header>
<slot name="header">
<!-- <h2>The Default</h2> -->
</slot>
</header>
<slot></slot>
</div>
</template>
- Bunu önlemek için header elementinde header slot olduğunda ekle şeklinde bir yapıya aşağıdaki şekilde yaparız.
BaseCard.vue
<template>
<div>
<header v-if="$slots.header">
<slot name="header">
<!-- <h2>The Default</h2> -->
</slot>
</header>
<slot></slot>
</div>
</template>
<script>
export default {
mounted() {
// for information purposes
console.log(this.$slots.header);
}
};
</script>
- Parent comp'de slot alanlarını belirtmek için
v-slot:header
yerine#header
şeklinde de kullanabiliriz !!!
UserInfo.vue
<template>
<section>
<!-- base-card componentine slot alanlarının içerikleri -->
<base-card>
<template #header>
<!-- ... -->
</template>
<template #default>
<!-- ... -->
</template>
</base-card>
</section>
</template>
- Problem : Child comp'de for döngüsü kullanılan bir element içerisinde slot bir alan kullandığımızda döngü değişkenine, slotu içeriğini belirten parent componentde döngü değişkenine nasıl ulaşacağız ?
CourseGoals.vue child component (imports slot content)
<template>
<ul>
<li v-for="goal in goals" :key="goal">
<slot><!-- parent elementde goal değişkenini kullanmak istiyoruz. --></slot>
</li>
</ul>
</template>
<script>
export default {
data() {
return {
goals: ['Finish the course', 'Learn Vue'],
};
},
};
</script>
Çözüm
- Child comp'de slot elementine v-bind ile degiskeni bind ederiz. slot içerisine goal değişkenini item olarak bind etmiş oluruz.
CourseGoals.vue
<template>
<ul>
<li v-for="goal in goals" :key="goal">
<!-- parent elementine item ve another-prop değişkenini yoluyoruz. -->
<slot :item="goal" :another-prop="..."></slot>
</li>
</ul>
</template>
<script>
export default {
data() {
return {
goals: ['Finish the course', 'Learn Vue'],
};
},
};
</script>
- Parent component, slot'a bind edilen değişkene nasıl ulaşacak ?
Parent component içerisindeki component elementinde #default attribune verilen isimdeki objeye bind edilir. Aşağıdaki örnekte "slotProps" objesine değişkenler bind edilir. slotProps objesinden değişkenlere ulaşırız.
App.vue(partial)
<course-goals #default="slotProps">
<h2>{{ slotProps.item }}</h2>
<p>{{ slotProps['another-prop'] }}</p>
</course-goals>
Note
- Parent comp'da, child component elementinde slot alanı, template ile belirtirlerken de #default kullanılabilir.
App.vue(partial)
<course-goals>
<template #default="slotProps">
<h2>{{ slotProps.item }}</h2>
<p>{{ slotProps['another-prop'] }}</p>
</template>
</course-goals>
Vue ya özel component elementi ile istediğimiz component'i dinamik olarak yerleştirebiliriz. hangi component'in yerleşeceğine is attribute ile belirlenir. burada is attribute na binding yapılmış.
<template>
<div>
<the-header></the-header>
<!-- <TheHeader /> -->
<button @click="setSelectedComponent('active-goals')">Active Goals</button>
<button @click="setSelectedComponent('manage-goals')">Manage Goals</button>
<!-- old way -->
<!-- <active-goals v-if="selectedComponent === 'active-goals'"></active-goals>
<manage-goals v-if="selectedComponent === 'manage-goals'"></manage-goals>-->
<component :is="selectedComponent"></component>
</div>
</template>
<script>
// imports
/* ... */
export default {
components: {
TheHeader,
ActiveGoals,
ManageGoals,
},
data() {
return {
selectedComponent: 'active-goals'
};
},
methods: {
setSelectedComponent(cmp) {
this.selectedComponent = cmp;
},
},
};
</script>
ManageGoals.vue
<template>
<div>
<h2>Manage Goals</h2>
<input type="text" />
</div>
</template>
Note
ManageGoals componentinde olduğu gibi vue'ya config object vermezsek, otomatik kendisi oluşturur.
Yukarıdaki örnekte dinamik component'i değiştirip, tekrar eski component'e dönersek text input ların içerisi sıfırlanıyordu. aynı kalmasını istersek keep-alive özel elementini kullanırız.
App.vue(partial)
<manage-goals v-if="selectedComponent === 'manage-goals'"></manage-goals>
<keep-alive>
<component :is="selectedComponent"></component>
</keep-alive>
Böylelikle keep-alive, component'in state'ni koruruz. arka planda vue destroy etmez.
Eğer bir dialog penceresi kodlarımızın ortasında açarsak semantik olarak dogru olmaz. Dogru olan body elementinin başında veya sonunda olmalı. bunu yapmak vue nun özel teleport elementini kullanırız. aşağıdaki örnek body'nin sonuna taşıyor dialog elementini.
<template>
<div>
<h2>Manage Goals</h2>
<input type="text" ref="goal" />
<button @click="setGoal">Set Goal</button>
<teleport to="body">
<error-alert v-if="inputIsInvalid">
<h2>Input is invalid!</h2>
<p>Please enter at least a few characters...</p>
<button @click="confirmError">Okay</button>
</error-alert>
</teleport>
</div>
</template>
<script>
import ErrorAlert from './ErrorAlert.vue';
export default {
components: {
ErrorAlert
},
data() {
return {
inputIsInvalid: false
};
},
methods: {
setGoal() {
const enteredValue = this.$refs.goal.value;
if (enteredValue === '') {
this.inputIsInvalid = true;
}
},
confirmError() {
this.inputIsInvalid = false;
}
}
}
</script>
ErrorAlert.vue
<template>
<dialog open>
<slot></slot>
</dialog>
</template>
<style scoped>
dialog {
margin: 0;
position: fixed;
top: 20vh;
left: 30%;
width: 40%;
background-color: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
padding: 1rem;
}
</style>
Önceki vue versiyonda bir component de sadece bir tane top element olurdu , bu da div olurdu. vue 3 ile beraber birden fazla top element olabiliyor.
ManageGoals.vue
<template>
<h2>Manage Goals</h2>
<input type="text" ref="goal" />
<button @click="setGoal">Set Goal</button>
<teleport to="body">
<error-alert v-if="inputIsInvalid">
<h2>Input is invalid!</h2>
<p>Please enter at least a few characters...</p>
<button @click="confirmError">Okay</button>
</error-alert>
</teleport>
</template>
Vue.js official documentation 'daki style guide mutlaka okunmalı. Strongly recommended kısmı daha öncelikli okunmalı.
-
Temel component'lerin başına Base veya App eklenmeli. BaseButton,BaseIcon,BaseTable veya AppButton, AppIcon gibi...
-
Eğer component tek bir yerde kullanılıyorsa başına The konulmalı.
- Componentleri ilgili dizinleri ayrıştırırsak daha düzenli olur. örneğin UI veya Base , Layout , cart , checkout (checkout ile ilgili comp'lar koyulur) gibi...
-- end --