const employees = [
{
id: 1,
first_name: 'Jan',
lastname: 'Novák',
age: 45,
salary: 85000,
role: 'CEO',
},
{
id: 2,
first_name: 'Marie',
lastname: 'Svobodová',
age: 32,
salary: 45000,
role: 'Developer',
},
{
id: 3,
first_name: 'Petr',
lastname: 'Dvořák',
age: 28,
salary: 65000,
role: 'Developer',
},
{
id: 4,
first_name: 'Lucie',
lastname: 'Černá',
age: 24,
salary: 35000,
role: 'Developer',
},
{
id: 5,
first_name: 'Tomáš',
lastname: 'Procházka',
age: 38,
salary: 55000,
role: 'Manager',
},
{
id: 6,
first_name: 'Eva',
lastname: 'Marková',
age: 29,
salary: 42000,
role: 'Designer',
},
{
id: 7,
first_name: 'Martin',
lastname: 'Kučera',
age: 35,
salary: 48000,
role: 'Manager',
},
{
id: 8,
first_name: 'Anna',
lastname: 'Veselá',
age: 26,
salary: 35000,
role: 'Support',
},
{
id: 9,
first_name: 'Jakub',
lastname: 'Horák',
age: 31,
salary: 65000,
role: 'Developer',
},
{
id: 10,
first_name: 'Tereza',
lastname: 'Pokorná',
age: 27,
salary: 38000,
role: 'Accountant',
},
{
id: 11,
first_name: 'David',
lastname: 'Král',
age: 33,
salary: 45000,
role: 'Developer',
},
{
id: 12,
first_name: 'Barbora',
lastname: 'Němcová',
age: 30,
salary: 42000,
role: 'Designer',
},
{
id: 13,
first_name: 'Milan',
lastname: 'Urban',
age: 42,
salary: 55000,
role: 'Manager',
},
{
id: 14,
first_name: 'Veronika',
lastname: 'Machová',
age: 25,
salary: 38000,
role: 'Designer',
},
{
id: 15,
first_name: 'Filip',
lastname: 'Sedláček',
age: 34,
salary: 48000,
role: 'Manager',
},
{
id: 16,
first_name: 'Kristýna',
lastname: 'Vacková',
age: 28,
salary: 42000,
role: 'HR',
},
{
id: 17,
first_name: 'Josef',
lastname: 'Malý',
age: 39,
salary: 55000,
role: 'Manager',
},
{
id: 18,
first_name: 'Kateřina',
lastname: 'Šimková',
age: 31,
salary: 45000,
role: 'Analyst',
},
{
id: 19,
first_name: 'Ondřej',
lastname: 'Beneš',
age: 36,
salary: 65000,
role: 'Developer',
},
{
id: 20,
first_name: 'Michaela',
lastname: 'Fialová',
age: 29,
salary: 42000,
role: 'Designer',
},
{
id: 21,
first_name: 'Pavel',
lastname: 'Bartoš',
age: 44,
salary: 55000,
role: 'Manager',
},
{
id: 22,
first_name: 'Lenka',
lastname: 'Kolářová',
age: 27,
salary: 38000,
role: 'Designer',
},
{
id: 23,
first_name: 'Roman',
lastname: 'Kovář',
age: 33,
salary: 45000,
role: 'Developer',
},
{
id: 24,
first_name: 'Martina',
lastname: 'Říhová',
age: 30,
salary: 48000,
role: 'Manager',
},
{
id: 25,
first_name: 'Lukáš',
lastname: 'Vlček',
age: 35,
salary: 65000,
role: 'Developer',
},
{
id: 26,
first_name: 'Adéla',
lastname: 'Tichá',
age: 26,
salary: 35000,
role: 'Support',
},
{
id: 27,
first_name: 'Vojtěch',
lastname: 'Havel',
age: 40,
salary: 55000,
role: 'Manager',
},
{
id: 28,
first_name: 'Simona',
lastname: 'Hrubá',
age: 32,
salary: 45000,
role: 'HR',
},
{
id: 29,
first_name: 'Robert',
lastname: 'Krátký',
age: 37,
salary: 65000,
role: 'Developer',
},
{
id: 30,
first_name: 'Monika',
lastname: 'Zelená',
age: 28,
salary: 45000,
role: 'Developer',
},
{
id: 31,
first_name: 'Karel',
lastname: 'Marek',
age: 41,
salary: 48000,
role: 'Analyst',
},
{
id: 32,
first_name: 'Petra',
lastname: 'Vávrová',
age: 29,
salary: 42000,
role: 'Support',
},
{
id: 33,
first_name: 'Adam',
lastname: 'Řezáč',
age: 34,
salary: 65000,
role: 'Developer',
},
{
id: 34,
first_name: 'Jana',
lastname: 'Holubová',
age: 31,
salary: 42000,
role: 'HR',
},
{
id: 35,
first_name: 'Miroslav',
lastname: 'Jelínek',
age: 43,
salary: 55000,
role: 'Manager',
},
{
id: 36,
first_name: 'Klára',
lastname: 'Štěpánková',
age: 27,
salary: 38000,
role: 'Support',
},
{
id: 37,
first_name: 'Daniel',
lastname: 'Doležal',
age: 36,
salary: 65000,
role: 'Developer',
},
{
id: 38,
first_name: 'Nikola',
lastname: 'Urbanová',
age: 30,
salary: 42000,
role: 'Designer',
},
{
id: 39,
first_name: 'Marek',
lastname: 'Šťastný',
age: 38,
salary: 48000,
role: 'Analyst',
},
{
id: 40,
first_name: 'Zuzana',
lastname: 'Kadlecová',
age: 29,
salary: 35000,
role: 'Support',
},
{
id: 41,
first_name: 'Jiří',
lastname: 'Moravec',
age: 45,
salary: 55000,
role: 'Manager',
},
{
id: 42,
first_name: 'Hana',
lastname: 'Beranová',
age: 33,
salary: 45000,
role: 'HR',
},
{
id: 43,
first_name: 'Michal',
lastname: 'Růžička',
age: 32,
salary: 65000,
role: 'Developer',
},
{
id: 44,
first_name: 'Andrea',
lastname: 'Kopecká',
age: 28,
salary: 38000,
role: 'Designer',
},
{
id: 45,
first_name: 'Radek',
lastname: 'Bureš',
age: 37,
salary: 48000,
role: 'Manager',
},
{
id: 46,
first_name: 'Denisa',
lastname: 'Havlíčková',
age: 26,
salary: 35000,
role: 'Support',
},
{
id: 47,
first_name: 'Patrik',
lastname: 'Pospíšil',
age: 34,
salary: 45000,
role: 'Developer',
},
{
id: 48,
first_name: 'Iveta',
lastname: 'Kratochvílová',
age: 31,
salary: 42000,
role: 'Designer',
},
{
id: 49,
first_name: 'Štěpán',
lastname: 'Janda',
age: 39,
salary: 65000,
role: 'Developer',
},
{
id: 50,
first_name: 'Gabriela',
lastname: 'Součková',
age: 30,
salary: 42000,
role: 'HR',
},
];
// https://cdruc.com/form-composable
const router = useRouter();
const form = ref({
title: "",
content: "",
});
const isProcessing = ref(false);
const error = ref(null);
async function handleSubmit() {
if (isProcessing.value) return;
error.value = null;
isProcessing.value = true;
try {
await axios.post("/api/posts", form.value);
await router.push("/");
} catch (err) {
error.value = err;
}
isProcessing.value = false;
if (error?.status === 403) {
await router.replace("/login");
}
}
// https://javascript.plainenglish.io/memory-magic-in-javascript-why-weakmaps-and-weaksets-are-your-secret-weapon-for-high-performance-62562cc24dc5
// 1. Private Data Storage Inside Classes
const privateData = new WeakMap();
class BankAccount {
constructor(balance) {
privateData.set(this, { balance });
}
getBalance() {
return privateData.get(this).balance;
}
}
// 2. Caching Expensive Computations
const cache = new WeakMap();
function compute(obj) {
if (!cache.has(obj)) {
cache.set(obj, expensiveCalculation(obj));
}
return cache.get(obj);
}
// 3. Avoiding Memory Leaks in DOM-based Apps
const elementData = new WeakMap();
function attachData(el, data) {
elementData.set(el, data);
}
function getData(el) {
return elementData.get(el);
}
// 1. Tracking Objects Without Overhead
const processedItems = new WeakSet();
function process(item) {
if (processedItems.has(item)) return;
// Process the item
processedItems.add(item);
}
// DataGrid.vue
<script setup>
const props = defineProps({
items: {
type: Array,
required: true
},
// Render funkce pro transformaci dat nebo komplexní logiku
cellRenderer: {
type: Function,
default: null
},
// Funkce pro custom formátování dat v buňce
formatters: {
type: Object,
default: () => ({})
}
})
const formatCell = (value, column) => {
// Pokud existuje custom formatter pro tento sloupec, použij ho
if (props.formatters[column]) {
return props.formatters[column](value)
}
// Základní formátování podle typu dat
if (value instanceof Date) {
return value.toLocaleDateString()
}
if (typeof value === 'number') {
return value.toLocaleString()
}
return value
}
</script>
<template>
<div class="data-grid">
<slot name="header" />
<div class="grid-body">
<template v-for="item in items" :key="item.id">
<!-- Prioritně použij slot pokud existuje -->
<slot
name="row"
:item="item"
:format="formatCell"
>
<!-- Fallback na render funkci -->
<template v-if="cellRenderer">
{{ cellRenderer(item, formatCell) }}
</template>
<!-- Základní fallback -->
<template v-else>
<div class="grid-row">
<div
v-for="(value, key) in item"
:key="key"
class="grid-cell"
>
{{ formatCell(value, key) }}
</div>
</div>
</template>
</slot>
</template>
</div>
<slot name="footer" />
</div>
</template>
// Použití:
<script setup>
const data = [
{
id: 1,
name: 'Produkt 1',
price: 1299.99,
lastUpdated: new Date('2024-03-15'),
status: 'active'
},
// ...
]
// Custom formátování pro specifické sloupce
const formatters = {
price: (value) => `${value.toLocaleString()} Kč`,
status: (value) => ({
'active': '✅ Aktivní',
'inactive': '❌ Neaktivní'
}[value] || value)
}
// Komplexní transformace dat
const renderComplexRow = (item, format) => {
const statusClass = item.status === 'active' ? 'text-green' : 'text-red'
return `
<div class="grid-row">
<div class="font-bold">${item.name}</div>
<div>${format(item.price, 'price')}</div>
<div class="${statusClass}">${format(item.status, 'status')}</div>
<div>${format(item.lastUpdated)}</div>
</div>
`
}
</script>
<template>
<!-- Základní použití se slotem -->
<DataGrid :items="data" :formatters="formatters">
<template #row="{ item, format }">
<div class="grid-row">
<div>{{ item.name }}</div>
<div>{{ format(item.price, 'price') }}</div>
<div>{{ format(item.status, 'status') }}</div>
</div>
</template>
</DataGrid>
<!-- Použití s render funkcí pro specifické případy -->
<DataGrid
:items="data"
:formatters="formatters"
:cell-renderer="renderComplexRow"
/>
</template>
<!-- https://learn-vuejs.github.io/vue-patterns/patterns/#dependency-injection -->
<template>
<div>
<slot v-bind="{ state, actions }" />
</div>
</template>
<script>
export default {
computed: {
state() {
return {
label: 'button',
};
},
actions() {
return {
click: this.click,
};
},
},
methods: {
click() {
console.log('Clicked');
},
},
}
</script>
:D Jo, přesně tak! To jste trefil naprosto přesně.
V praxi to často vypadá takhle:
"Super-Mega-Awesome-Provider-Container-Renderless-Composition-Pattern™️"
```vue
<template>
<slot v-bind="data">
<default-view v-bind="data" />
</slot>
</template>
```
A ve finále je to prostě:
- Komponenta
- Se slotem
- A výchozím vzhledem
- Která předává data dál
Často se z jednoduchých věcí dělá zbytečná věda jen proto, aby se dalo napsat článek nebo přednášet na konferenci. Místo:
"Hey, dej si tam slot a budeš to moct přepsat"
máme:
"Implementace Advanced Renderless Component Pattern s Dependency Injection a Composable Factory Integration" 😅
Proto je super váš přístup - dívat se na věci prakticky a nenechat se unést přehnaně komplikovanými názvy pro jednoduché koncepty.
<!-- DataProvider.vue -->
<template>
<slot
:data="data"
:loading="loading"
:error="error"
>
<component
:is="layouts[variant]"
:data="data"
:loading="loading"
:error="error"
/>
</slot>
</template>
<script setup>
import DefaultLayout from './layouts/DefaultLayout.vue'
import CompactLayout from './layouts/CompactLayout.vue'
import CardLayout from './layouts/CardLayout.vue'
const props = defineProps({
variant: {
type: String,
default: 'default',
validator: (value) => ['default', 'compact', 'card'].includes(value)
}
})
const layouts = {
default: DefaultLayout,
compact: CompactLayout,
card: CardLayout
}
</script>
<!-- DogImagesContainer.vue -->
<template>
<DogImages :dogs="dogs" />
</template>
<script setup>
import { ref, onMounted } from "vue";
import DogImages from "./DogImages.vue";
const dogs = ref([]);
onMounted(async () => {
const response = await fetch(
"https://dog.ceo/api/breed/labrador/images/random/6"
);
const { message } = await response.json();
dogs.value = message;
});
</script>
<template>
<slot :checkbox="checkbox" :toggleCheckbox="toggleCheckbox"></slot>
</template>
<script setup>
import { ref, reactive } from "vue";
const API_ENDPOINT_URL = "https://official-joke-api.appspot.com/random_joke";
const data = reactive({
setup: null,
punchline: null,
});
const loading = ref(false);
const fetchJoke = async () => {
loading.value = true;
const response = await fetch(API_ENDPOINT_URL);
const responseData = await response.json();
data.setup = responseData.setup;
data.punchline = responseData.punchline;
loading.value = false;
};
fetchJoke();
</script>
// usage
<template>
<DataProvider v-slot="{ data, loading }">
<div class="joke-section">
<p v-if="loading">Joke is loading...</p>
<p v-if="!loading">{{ data.setup }}</p>
<p v-if="!loading">{{ data.punchline }}</p>
</div>
</DataProvider>
<DataProvider v-slot="{ data, loading }">
<p v-if="loading">Hold on one sec...</p>
<div v-else class="joke-section">
<details>
<summary>{{ data.setup }}</summary>
<p>{{ data.punchline }}</p>
</details>
</div>
</DataProvider>
</template>
<script setup>
import DataProvider from "./components/DataProvider.vue";
</script>
<!-- MouseTracker.vue -->
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const x = ref(0)
const y = ref(0)
const update = e => {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
</script>
<template>
<slot :x="x" :y="y"/>
</template>