// Noob
const eventHandler = e => {
if (e.key === 'Escape') {
this.closeGallery()
}
if (e.key === 'ArrowLeft') {
this.prevMedia()
}
if (e.key === 'ArrowRight') {
this.nextMedia()
}
}
// Profi
const eventHandler = e => {
const keyActions = {
Escape: closeGallery,
ArrowLeft: prevMedia,
ArrowRight: nextMedia
}
keyActions[e.key]?.()
}
<template>
<div>
<ul>
<li v-for="item in cartItems" :key="item.id">
{{ item.name }} - {{ item.price }} - Quantity: {{ item.quantity }}
</li>
</ul>
<p>Total Price: {{ totalPrice }}</p>
<p>Total Quantity: {{ totalQuantity }}</p>
</div>
</template>
<script>
export default {
data() {
return {
cartItems: [
{ id: 1, name: 'Item 1', price: 10, quantity: 2 },
{ id: 2, name: 'Item 2', price: 15, quantity: 1 },
{ id: 3, name: 'Item 3', price: 20, quantity: 3 }
]
};
},
computed: {
totalPrice() {
return this.cartItems.reduce((total, item) => total + item.price * item.quantity, 0);
},
totalQuantity() {
return this.cartItems.reduce((total, item) => total + item.quantity, 0);
}
}
};
</script>
// my version - inspired by https://michaelnthiessen.com/stealing-prop-types/
// iconDefault.js
export const iconDefaults = {
size: 'medium',
shape: 'circle',
icon: 'default',
animation: 'none'
};
<!-- Icon.vue -->
<script setup>
import { iconDefaults } from './iconDefaults.js';
const props = defineProps({
size: { type: String, default: iconDefaults.size },
shape: { type: String, default: iconDefaults.shape },
icon: { type: String, default: iconDefaults.icon },
animation: { type: String, default: iconDefaults.animation }
});
</script>
<!-- Panel.vue -->
<script setup>
import Icon from './Icon.vue';
import { iconDefaults } from './iconDefaults.js';
import { computed } from 'vue';
const props = defineProps({
heading: String,
withIcons: Boolean,
iconConfig: {
type: Object,
default: () => ({ ...iconDefaults })
}
});
const finalIconConfig = computed(() => {
return { ...iconDefaults, ...props.iconConfig };
});
</script>
<!--ErrorBoundary.vue-->
<!-- https://vueschool.io/articles/vuejs-tutorials/what-is-a-vue-js-error-boundary-component/ -->
<script setup>
import { ref, computed } from 'vue'
// create an reactive container to store the potential error
const error = ref()
// using Vue's build in lifecycle hook
// listen for errors from components passed to the default slot
onErrorCaptured(err => {
// set the reactive error container to the thrown error
error.value = err
// return false to prevent error from bubbling further
// (this is optional, if you have a top level error reporter catching errors
// you probably don't want to do this.
// Alternatively you could report your errors in the boundary and prevent bubble
return false
})
// create a way to clear the error
function clearError() {
error.value = null
}
// provide the error and the clear error function to the slot
// for use in consuming component to display messaging
// and clear the error
const slotProps = computed(() => {
if (!error.value) return {}
return { error, clearError }
})
// if there's an error show the error slot, otherwise show the default slot
const slotName = computed(() => (error.value ? 'error' : 'default'))
</script>
<template>
<slot :name="slotName" v-bind="slotProps"></slot>
</template>
/*
onScopeDispose - Podobně jako onWatcherCleanup, ale obecnější.
Zavolá se, když je aktuální reaktivní efektový scope (effect scope) ukončen.
Je to užitečné pro čištění zdrojů v jakémkoliv reaktivním kontextu:
*/
// 1
import { onScopeDispose, effectScope } from 'vue'
// Vytvoření izolovaného scope
const scope = effectScope()
scope.run(() => {
// Kód uvnitř scope
onScopeDispose(() => {
// Tento kód se zavolá při scope.stop()
})
})
// Později můžete ukončit scope
scope.stop()
// 2)
const scope = effectScope()
scope.run(() => {
const state = reactive({ count: 0 })
const double = computed(() => state.count * 2)
watch(() => state.count, (count) => console.log(count))
})
// Později ukončí všechny reaktivní efekty
scope.stop()
// 1) cleanup when watch is ended
import { watch, onWatcherCleanup } from 'vue'
watch(id, (newId) => {
const { response, cancel } = doAsyncWork(newId)
// `cancel` is called if `id` changes or the component unmounts
onWatcherCleanup(cancel)
})
// 2) better watch with desctruct
const { pause, resume, stop } = watch(source, (newVal, oldVal) => {
// Watch logic
});
// Pause watching
pause();
// Resume watching
resume();
// 3 watch logic, not only true/false ...
watch(reactiveObject, (newVal, oldVal) => {
// Watch logic
}, { deep: 2 });
<!-- 1. PROPS -->
<!-- JS -->
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
foo: { type: String, required: true },
bar: Number,
});
// props.foo is a string
// props.bar is a number or undefined
</script>
<!-- TS -->
<script setup lang="ts">
const props = defineProps<{
foo: string;
bar?: number;
}>()
</script>
<script setup lang="ts">
interface Props {
foo: string;
bar?: number;
}
const props = defineProps<Props>()
</script>
<!-- 2. EMITS -->
<!-- JS -->
<script setup>
const emit = defineEmits(['change', 'update'])
</script>
<!-- TS -->
<script setup lang="ts">
const emit = defineEmits<{
change: [id: number]
update: [value: string]
}>()
</script>
<!-- 3. Typing Ref and Reactive Data => Inference -->
<script setup>
import { ref } from 'vue';
const count = ref(0);
count.value = 'string'; // error, inference, we expect number
</script>
<!-- 4. Typing Server Responses -->
<script setup lang="ts">
import { ref, onMounted } from 'vue';
interface User {
id: number;
name: string;
email: string;
}
const userData = ref<User | null>(null);
onMounted(async () => {
const response = await fetch('https://api.example.com/user');
const data: User = await response.json();
userData.value = data; // TypeScript ensures data usages match the User interface
});
</script>
<!-- 5. Typing Computed Data -->
import { ref, computed } from 'vue'
const count = ref(0)
// inferred type: ComputedRef<number>
const double = computed(() => count.value * 2)
// => TS Error: Property 'split' does not exist on type 'number'
const result = double.value.split('')
// or explicitely define return type
const double = computed<number>(() => {
// type error if this doesn't return a number
})
<!-- 6. Typing Scoped Slots-->
<template>
<slot :msg="message"></slot>
</template>
<script setup lang="ts">
const message = 'Hello, Vue!';
const slots = defineSlots<{
default: (props: { msg: string }) => any;
}>()
</script>
<!-- 7. Typing Template Refs -->
<script setup lang="ts">
import { useTemplateRef, onMounted } from 'vue';
const myInput = useTemplateRef('my-input');
onMounted(() => {
myInput.value?.focus();
});
</script>
<template>
<input ref="my-input" />
</template>
<!-- 8. Typing Provide/Inject -->
<!-- ParentComponent.vue -->
<script setup lang="ts">
import { provide } from 'vue';
const theme = 'dark';
provide('theme', theme);
</script>
<!-- ChildComponent.vue -->
<script setup lang="ts">
import { inject } from 'vue';
const theme = inject<string>('theme'); // Inject with expected type
// TypeScript ensures that 'theme' is of type string
</script>
<!-- 9. Generics -->
<script setup lang="ts" generic="T">
defineProps<{
items: T[]; // Array of items of type T
selected: T; // Single selected item of type T
}>()
</script>
<!-- 10. Typed Composables -->
// useUser.ts
import { ref } from 'vue';
interface User {
id: number;
name: string;
age: number;
}
export function useUser() {
const user = ref<User | null>(null);
function fetchUser(id: number) {
// Fetching user logic
user.value = { id, name: 'John Doe', age: 30 };
}
return { user, fetchUser };
}
<!-- https://www.vuemastery.com/courses/component-design-patterns/one-object-to-rule-them-all -->
<!-- To heavy , to complicated, all props we dont need ...-->
<template>
<main>
<Component
v-for="content in apiResponse"
:key="content.id"
:is="content.type"
:article-title="content.title"
:article-content="content.body"
:ad-image="content.image"
:ad-heading="content.heading"
@click="content.type === 'NewsArticle' ? openArticle : openAd"
@mouseover="content.type === 'NewsArticle' ? showPreview : trackAdEvent"
/>
</main>
</template>
<!--Much better -->
<template>
<main>
<Component
v-for="content in apiResponse"
:key="content.id"
:is="content.type"
v-bind="feedItem(content).attrs"
v-on="feedItem(content).events"
/>
</main>
</template>
<script>
export default {
methods: {
feedItem(item) {
if (item.type === 'NewsArticle') {
return {
attrs: {
'article-title': item.title,
'article-content': item.content
},
events: {
click: this.openArticle,
mouseover: this.showPreview
}
}
} else if (item.type === 'NewsAd') {
return {
attrs: {
'ad-image': item.image,
'ad-heading': item.heading
},
events: {
click: this.openAd,
mouseover: this.trackAdEvent
}
}
}
}
}
}
</script>
<script setup>
import { useSlots, onMounted, ref } from "vue";
const test = ref()
onMounted(() => {
// 1
console.log(test.value.children[0])
// 2 nebo bez refu primo ze slotu useSlots().default()[0].el
})
</script>
<template>
<!-- nejde ref umistit primo na slot, nutno to takto obalit, nebo viz druhy zpusob -->
<div ref="test">
<slot />
</div>
</template>
// 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");
}
}