/ Gists / Vue.js

Gists - Vue.js

On gists

ES6 dynamic import / export

Vue.js

ex.js #

// app.js

async function loadMathModule() {
  const mathModule = await import("./math.js");
  console.log(mathModule.add(5, 3));
}

// Or with .then()
import("./math.js")
  .then((mathModule) => {
      console.log(mathModule.PI);
  })
  .catch((error) => {
      console.error("Failed to load module:", error);
  });

On gists

Dynamic component (h or :is with v-bind)

Vue.js

Dynamic.vue #

<!-- 1 -->
<component
    v-if="transport?.image"
    :is="typeof transport.image === 'object' ? 'aw-img' : 'img'"
    v-bind="
      typeof transport.image === 'object'
        ? { image: transport.image, size: 'eop-icon', style: 'max-width: 40px' }
        : { src: transport.image, size: 'eop-icon', style: 'max-width: 40px' }
    "
  />
  
  
  <!-- 2 h render -->
 const transportImage = computed(() => {
      if (!props.transport?.image) return null

      if (typeof props.transport.image === 'object') {
        return h(resolveComponent('aw-img'), {
          image: props.transport.image,
          size: 'eop-icon',
          style: 'max-width: 40px'
        })
      } else {
        return h('img', {
          src: props.transport.image,
          size: 'eop-icon',
          style: 'max-width: 40px'
        })
      }
    })

On gists

V-memo

Vue.js

v-memo.js #

// https://learnvue.co/articles/v-once-v-memo

<script setup>
import { ref } from 'vue'

const subscribers = ref(4000)
const views = ref(10000)
const likes = ref(3000)
</script>
<template>
  <div>
    <div v-memo="[subscribers]">
      <p>Subscribers: {{ subscribers }}</p>
      <p>Views: {{ views }}</p>
      <p>Likes: {{ likes }}</p>
    </div>
    <button @click="subscribers++">Subscribers++</button>
    <button @click="views++">Views++</button>
    <button @click="likes++">Likes++</button>
    <div>
      <p>Current state:</p>
      <p>Subscribers: {{ subscribers }}</p>
      <p>Views: {{ views }}</p>
      <p>Likes: {{ likes }}</p>
    </div>
  </div>
</template>

On gists

Reactivity in composables

Popular ⭐ Vue.js

App.vue #

<template>
  <TheBase :state="state" />
</template>


<script setup>
import { ref } from 'vue'
import TheBase from './TheBase.vue'


const state = ref(111)

setTimeout(() => {
  state.value = 222
}, 2000)
</script>

On gists

Slots drilling

Vue.js

better-way.vue #

<!-- Parent -->
  <script setup>
    provide('transportFree', useSlots().transportfree)  // slot existuje i kdyz ho vubec nepouziju v komponente, ve smyslu <slot name="transportfree" />, staci zvenci <template #transportfree>content</template>
 </script>
 
 <!-- Child -->
 <script setup>
    const transportFree = inject('transportFree')
 </script>
 <template>
    <component :is="transportFree" />
 </template>

On gists

Compute & transform logic outside of UI component

Popular ⭐ Vue.js

index.md #

Vue.js Media Transformer - Všechny varianty implementace

1. Composable (Vue 3 Composition API)

// composables/useMediaObjects.js
export function useMediaObjects() {
  const transformMediaObjects = (productGallery, productVideos) => {
    const finalObjects = []

    // Photos
    if (productGallery) {
      finalObjects.push(
        ...productGallery.map((photo, index) => ({
          ...photo,
          type: 'photo',
          rank: index + 1
        }))
      )
    }

    // Videos
    if (productVideos) {
      finalObjects.push(
        ...productVideos.map((videoUrl, index) => {
          const youtubeId = extractYouTubeId(videoUrl)
          return {
            url: videoUrl,
            type: 'video',
            rank: finalObjects.length + index + 1,
            previewThumb: youtubeId 
              ? `https://img.youtube.com/vi/${youtubeId}/sddefault.jpg` 
              : null
          }
        })
      )
    }

    return finalObjects
  }

  const extractYouTubeId = (url) => {
    return url.match(
      /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/
    )?.[1]
  }

  return {
    transformMediaObjects,
    extractYouTubeId
  }
}

Použití v komponentě:

// Product.vue
<script setup>
import { computed } from 'vue'
import { useMediaObjects } from '@/composables/useMediaObjects'

const props = defineProps(['productGallery', 'product'])

const { transformMediaObjects } = useMediaObjects()

const mediaObjects = computed(() => {
  return transformMediaObjects(props.productGallery, props.product?.product_video)
})
</script>

2. JS Třída - Statické metody

// services/MediaTransformer.js
export class MediaTransformer {
  static transform(productGallery, productVideos) {
    const finalObjects = []

    if (productGallery) {
      finalObjects.push(
        ...productGallery.map((photo, index) => ({
          ...photo,
          type: 'photo',
          rank: index + 1
        }))
      )
    }

    if (productVideos) {
      finalObjects.push(
        ...productVideos.map((videoUrl, index) => {
          const youtubeId = this.extractYouTubeId(videoUrl)
          return {
            url: videoUrl,
            type: 'video',
            rank: finalObjects.length + index + 1,
            previewThumb: youtubeId 
              ? `https://img.youtube.com/vi/${youtubeId}/sddefault.jpg` 
              : null
          }
        })
      )
    }

    return finalObjects
  }

  static extractYouTubeId(url) {
    return url.match(
      /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/
    )?.[1]
  }

  static generateThumbnail(youtubeId, quality = 'sddefault') {
    return `https://img.youtube.com/vi/${youtubeId}/${quality}.jpg`
  }
}

Použití:

// Product.vue
<script setup>
import { computed } from 'vue'
import { MediaTransformer } from '@/services/MediaTransformer'

const props = defineProps(['productGallery', 'product'])

const mediaObjects = computed(() => {
  return MediaTransformer.transform(props.productGallery, props.product?.product_video)
})
</script>

3. JS Třída - Export třídy (instance v komponentě)

// services/MediaTransformer.js
export class MediaTransformer {
  constructor(config = {}) {
    this.thumbnailQuality = config.thumbnailQuality || 'sddefault'
    this.defaultVideoType = config.defaultVideoType || 'video'
  }

  transform(productGallery, productVideos) {
    const finalObjects = []

    if (productGallery) {
      finalObjects.push(
        ...productGallery.map((photo, index) => ({
          ...photo,
          type: 'photo',
          rank: index + 1
        }))
      )
    }

    if (productVideos) {
      finalObjects.push(
        ...productVideos.map((videoUrl, index) => {
          const youtubeId = this.extractYouTubeId(videoUrl)
          return {
            url: videoUrl,
            type: this.defaultVideoType,
            rank: finalObjects.length + index + 1,
            previewThumb: youtubeId 
              ? `https://img.youtube.com/vi/${youtubeId}/${this.thumbnailQuality}.jpg` 
              : null
          }
        })
      )
    }

    return finalObjects
  }

  extractYouTubeId(url) {
    return url.match(
      /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/
    )?.[1]
  }

  setConfig(newConfig) {
    Object.assign(this, newConfig)
    return this
  }
}

Použití:

// Product.vue
<script setup>
import { computed, ref } from 'vue'
import { MediaTransformer } from '@/services/MediaTransformer'

const props = defineProps(['productGallery', 'product'])

const mediaTransformer = ref(new MediaTransformer({ thumbnailQuality: 'hqdefault' }))

const mediaObjects = computed(() => {
  return mediaTransformer.value.transform(props.productGallery, props.product?.product_video)
})
</script>

4. JS Třída - Export instance (singleton)

// services/MediaTransformer.js
class MediaTransformer {
  constructor(config = {}) {
    this.thumbnailQuality = config.thumbnailQuality || 'sddefault'
  }

  transform(productGallery, productVideos) {
    // stejná implementace jako výše...
  }

  extractYouTubeId(url) {
    // stejná implementace...
  }

  setConfig(newConfig) {
    Object.assign(this, newConfig)
    return this
  }
}

// Export instance
export const mediaTransformer = new MediaTransformer()

// Případně export obou
export { MediaTransformer }

Použití:

// Product.vue
<script setup>
import { computed } from 'vue'
import { mediaTransformer } from '@/services/MediaTransformer'

const props = defineProps(['productGallery', 'product'])

const mediaObjects = computed(() => {
  return mediaTransformer.transform(props.productGallery, props.product?.product_video)
})

// Změna konfigurace kdykoliv:
// mediaTransformer.setConfig({ thumbnailQuality: 'maxresdefault' })
</script>

5. Singleton Pattern

// services/MediaTransformer.js
class MediaTransformer {
  static _instance = null

  constructor(config = {}) {
    if (MediaTransformer._instance) {
      return MediaTransformer._instance
    }

    this.thumbnailQuality = config.thumbnailQuality || 'sddefault'
    MediaTransformer._instance = this
  }

  static getInstance(config = {}) {
    if (!this._instance) {
      this._instance = new MediaTransformer(config)
    }
    return this._instance
  }

  transform(productGallery, productVideos) {
    // implementace...
  }

  extractYouTubeId(url) {
    // implementace...
  }
}

export default MediaTransformer

Použití:

// Product.vue
<script setup>
import { computed } from 'vue'
import MediaTransformer from '@/services/MediaTransformer'

const props = defineProps(['productGallery', 'product'])

const mediaObjects = computed(() => {
  const transformer = MediaTransformer.getInstance({ thumbnailQuality: 'hqdefault' })
  return transformer.transform(props.productGallery, props.product?.product_video)
})
</script>

6. Factory Function

// services/MediaTransformer.js
class MediaTransformer {
  constructor(config = {}) {
    this.thumbnailQuality = config.thumbnailQuality || 'sddefault'
  }

  transform(productGallery, productVideos) {
    // implementace...
  }

  extractYouTubeId(url) {
    // implementace...
  }
}

export const createMediaTransformer = (config = {}) => {
  return new MediaTransformer(config)
}

// Export i třídy pro pokročilé použití
export { MediaTransformer }

Použití:

// Product.vue
<script setup>
import { computed, ref } from 'vue'
import { createMediaTransformer } from '@/services/MediaTransformer'

const props = defineProps(['productGallery', 'product'])

const mediaTransformer = ref(createMediaTransformer({ thumbnailQuality: 'maxresdefault' }))

const mediaObjects = computed(() => {
  return mediaTransformer.value.transform(props.productGallery, props.product?.product_video)
})
</script>

7. Plain Object/Service

// services/mediaService.js
export const mediaService = {
  defaultConfig: {
    thumbnailQuality: 'sddefault'
  },

  transform(productGallery, productVideos, config = {}) {
    const finalConfig = { ...this.defaultConfig, ...config }
    const finalObjects = []

    if (productGallery) {
      finalObjects.push(
        ...productGallery.map((photo, index) => ({
          ...photo,
          type: 'photo',
          rank: index + 1
        }))
      )
    }

    if (productVideos) {
      finalObjects.push(
        ...productVideos.map((videoUrl, index) => {
          const youtubeId = this.extractYouTubeId(videoUrl)
          return {
            url: videoUrl,
            type: 'video',
            rank: finalObjects.length + index + 1,
            previewThumb: youtubeId 
              ? `https://img.youtube.com/vi/${youtubeId}/${finalConfig.thumbnailQuality}.jpg` 
              : null
          }
        })
      )
    }

    return finalObjects
  },

  extractYouTubeId(url) {
    return url.match(
      /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/
    )?.[1]
  }
}

Použití:

// Product.vue
<script setup>
import { computed } from 'vue'
import { mediaService } from '@/services/mediaService'

const props = defineProps(['productGallery', 'product'])

const mediaObjects = computed(() => {
  return mediaService.transform(
    props.productGallery, 
    props.product?.product_video,
    { thumbnailQuality: 'hqdefault' }
  )
})
</script>

8. Pure Functions (Helper)

// utils/mediaTransform.js
export const transformMediaObjects = (productGallery, productVideos, config = {}) => {
  const { thumbnailQuality = 'sddefault' } = config
  const finalObjects = []

  if (productGallery) {
    finalObjects.push(
      ...productGallery.map((photo, index) => ({
        ...photo,
        type: 'photo',
        rank: index + 1
      }))
    )
  }

  if (productVideos) {
    finalObjects.push(
      ...productVideos.map((videoUrl, index) => {
        const youtubeId = extractYouTubeId(videoUrl)
        return {
          url: videoUrl,
          type: 'video',
          rank: finalObjects.length + index + 1,
          previewThumb: youtubeId 
            ? `https://img.youtube.com/vi/${youtubeId}/${thumbnailQuality}.jpg` 
            : null
        }
      })
    )
  }

  return finalObjects
}

export const extractYouTubeId = (url) => {
  return url.match(
    /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/
  )?.[1]
}

Použití:

// Product.vue
<script setup>
import { computed } from 'vue'
import { transformMediaObjects } from '@/utils/mediaTransform'

const props = defineProps(['productGallery', 'product'])

const mediaObjects = computed(() => {
  return transformMediaObjects(
    props.productGallery, 
    props.product?.product_video,
    { thumbnailQuality: 'hqdefault' }
  )
})
</script>

Srovnání variant

Varianta Výhody Nevýhody Použití
Composable Vue 3 native, reaktivita, auto-import Vue specific Vue 3 projekty
Statické metody Jednoduché, žádná konfigurace Není flexibilní Jednoduché transformace
Export třídy Flexibilní konfigurace Musíš vytvářet instance Různé konfigurace
Export instance Žádné new, sdílený stav Pevná konfigurace Jednotná konfigurace
Singleton Jedna instance, lazy loading Složitější pattern Globální konfigurace
Factory Flexibilní vytváření Extra abstrakce Komplexní objekty
Plain Object Jednoduché, framework agnostic Není OOP Malé utility
Pure Functions Nejjednodušší, testovatelné Žádná organizace Funkcionální přístup

Doporučení

  • Pro jednoduché transformace: Pure Functions nebo Statické metody
  • Pro flexibilní konfiguraci: Export třídy nebo Factory
  • Pro Vue 3 projekty: Composable
  • Pro globální použití: Export instance s setConfig

On gists

Vue parent - child

Popular ⭐ Vue.js

vue1.js #

// https://play.vuejs.org/#eNqdVE2P2jAQ/SsjXwCJBlXtic2itohDK7W72u7Rl5BMwItjW7bDUiH+e8d2krJbRNXlgOyZN1/Pb3Jkn43J9i2yOctdaYXx4NC3ZsGVaIy2Ho5gsZ5C6/ARGyMLjw9YwwlqqxsYUehogH7XVSE7RzaLt5CbAKVWzkMTLCH69lW68Si6RpMbrvJZ6oM6oIvvQHS7LywqD/nawoyut3//Bl++br3XCj6VUpS7W876ypk2qMYTzhZ3dEgN57OEvhpXSu0wBi7D6UJkqNz9b7suEh9EX5+Js+SYDWOxKfOO2KnFJntyWtE7HLkC4KzUjRES7Z3xgtjjbA7RE3yFlPr5W7R52+K0t5dbLHcX7E/uEGyc3Vt0aPfI2eDzhd2gT+7Vzx94oPPgpK5bSegrzgd0WrahxwT70qqK2j7DxW6/RokItXl0q4NH5fqhQqMBeYp4zkgwyyuj/2n3Q/YxxnF1IhYHtQUln6kGYLkVsoJxBEzeJp//VM0/xVKJPezfua1+JrRwIStJo5SFc2SI8KVWxBINukgq6u75jGJfSijuyZXdfbWr/TKmsrSKBBnXhXQ44apuVRk4hzRxoj0hs30hWyR8eDAg0s/Q3cAX4TF1wnNVYS0Urg4m4I+xyjRFw4nKv9x953/J9ITZOSW9FkxRVaSnOby32NzAuih3G6tJfXMwsm3oUxJQDelFqA7U6YXKxNTs9BvEAbCg

// Parent
<script setup>
import { ref, useTemplateRef } from 'vue'
import Modal from './Modal.vue'
const modalRef = useTemplateRef('modal');
</script>

<template>
Parent <br />
=================== <br />
<button @click="modalRef.open()">Open Modal</button>
<button @click="modalRef.close()">Close Modal</button>
<br /><br /><hr />
<Modal ref="modal" />
</template>

// Child - Modal
<template>
  Child (Modal) <br />
=================== <br />
<button @click="open()">Open Modal</button>
<button @click="close()">Close Modal</button>
<div v-show="isOpen" class="ModalContent">Modal Content</div>
</template>

<script setup>
import { ref } from 'vue'

const isOpen = ref(false)
function open() {
  isOpen.value = true 
}
function close() {
  isOpen.value = false 
}

defineExpose({ open, close })
</script>

<style>
  .ModalContent {
    padding: 1rem; background: plum;
    margin: 1rem;
  }
</style>

On gists

Vue computed with private variable via IIFE

JS Patterns Vue.js

any-component.js #

const showThumbs = (() => {
  // really private ;-)
  const state = ref(props.enableThumbs)

  return computed({
    get: () => state.value,
    set: value => {
      state.value = value
    }
  })
  
})()

/*

-----------------------------------
Nejčastější použití je právě pro:
  - Vytvoření privátního scope
  - Enkapsulaci dat a logiky
  - Vyhnutí se globálnímu scope
  - Zachování stavu přes closure
 -----------------------------------
 * 1. Vytvoření privátního scope
 * - proměnné uvnitř neuniknou ven
 * - čisté API pro vnější svět
 */
const counter = (() => {
  let count = 0  // privátní proměnná
  return {
    increment() { count++ },
    getCount() { return count }
  }
})()

/**
 * 2. Vyhnutí se globálnímu scope
 * - dočasné proměnné zůstanou lokální
 * - neznečišťuje globální namespace
 */
(() => {
  const temp = 'nějaká dočasná proměnná'
  // temp existuje jen uvnitř, neznečišťuje globální scope
})()

/**
 * 3. Modulární pattern
 * - privátní metody a proměnné
 * - veřejné API
 */
const module = (() => {
  const privateVar = 'private'
  const privateMethod = () => { 
    console.log(privateVar) 
  }
  
  return {
    publicMethod() {
      privateMethod()  // má přístup k privátním věcem
    }
  }
})()

/**
 * 4. Closure pro zachování stavu
 * - privátní stav
 * - reaktivní hodnota
 */
const showThumbs = (() => {
  const state = ref(props.enableThumbs)  // privátní stav
  return computed({
    get: () => state.value,
    set: (value) => {
      state.value = value
    }
  })
})()

/**
 * 5. Vyhnutí se konfliktům
 * - lokální aliasy
 * - izolace kódu
 */
(() => {
  const $ = jQuery  // lokální alias pro jQuery
  // používání $ uvnitř neovlivní jiné knihovny používající $
})()

/**
 * 6. Inicializace
 * - nastavení při načtení
 * - konfigurace
 * - event listenery
 */
(() => {
  // nějaká inicializace
  const config = { 
    apiKey: 'xxx',
    endpoint: 'https://api.example.com'
  }
  
  // nastavení event listenerů
  document.addEventListener('DOMContentLoaded', () => {
    // inicializace UI
  })
  
  // další setup
  const init = () => {
    // ...
  }
  
  init()
})()

On gists

Advanced tailwind tricks by shadcn

Tailwind CSS Vue.js

examples.js #

// https://www.reddit.com/r/tailwindcss/comments/1icfwbo/here_are_10_tailwind_tricks_shared_by_shadcn_they/
// https://x.com/shadcn/status/1842329158879420864


// 1. Dynamic CSS Variables in Tailwind
<div style={{ "--width": isCollapsed ? "8rem" : "14rem" }} className="w-[--width] transition-all" />

// 2. Data Attribute State Management
<div 
  data-state={isOpen ? "open" : "closed"}
  className="data-[state=open]:bg-blue-500"
/>


// 3. Nested SVG Controll
<div 
  data-collapsed={isCollapsed}
  className="[&[data-collapsed=true]_svg]:rotate-180"
>
  <svg>...</svg>
</div>


// 4. Parent-Child Style Inheritance
<section :data-collapsed="isCollapsed"
  <div className="[[data-collapsed=true]_&]:rotate-180">
    {/* Child inherits rotation when parent has data-collapsed=true */}
  </div>
</section>


// 5. Group Data States
<div className="group" data-collapsed={isCollapsed}>
  <div className="group-data-[collapsed=true]:rotate-180"/>
</div>


// 6. Data Slots
<div className="data-[slot=action]:*:hover:mr-0">
  <div data-slot="action" class="-mr-10">...</div>
</div>


// 7. Peer Element Control
<button className="peer" :data-active="isActive">Menu</button>
<div className="peer-data-[active=true]:bg-blue-500"/>


// 8. Named Group Focus
<div className="group/menu">
  <button className="group-focus-within/menu:bg-blue-500"/>
</div>


// 9. Group Has Selectors
<div className="group/menu">
  <div className="group-has-[[data-active=true]]/menu:bg-blue-500"/>
</div>


// 10. Variant Props
<button 
  data-variant={variant}
  className="data-[variant=ghost]:border-blue-500"
/>

On gists

Reactive like unwrap on first level

Vue.js

explanation.js #

// 1 Když neobalíš objekt do reactive():

const filtersStorage = {
  priceValue: computed({
    get: () => selectedFilters.value.priceValue,
    set: v => selectedFilters.value.priceValue = +v
  })
}

// Pak filtersStorage.priceValue je přímo ComputedRef objekt. A ComputedRef se chová jako ref - musíš použít .value pro přístup k hodnotě:

// Bez reactive() wrapper
filtersStorage.priceValue.value // ✅ musíš použít .value


// 2
// Když obalíš objekt do reactive():
const filtersStorage = reactive({
  priceValue: computed(...)
})

// S reactive() wrapper
filtersStorage.priceValue // ✅ .value se přidá automaticky


/*
Proč se to děje?

reactive() má speciální chování - automaticky unwrapuje refs na prvním levelu objektu
Tohle je záměrné chování pro lepší DX (developer experience)
Bez reactive() wrapper pracuješ přímo s ComputedRef objektem

*/