Chuyển đến nội dung chính

Bài 18: Advanced Patterns & Custom Module

Renderless components, higher-order components. Vue virtual DOM, render functions. Creating Nuxt modules, kit utilities. Nuxt layers, extending Nuxt applications.

💻 Lập trình — Bài 18 Bài 18: Advanced Patterns & Custom Module

Vue.js & Nuxt: Từ Cơ bản đến Nâng cao

Phần 5: Advanced Patterns

xdev.asia

1. Renderless Components

<!-- components/FetchData.vue — renderless -->
<script setup lang="ts" generic="T">
import { ref, watchEffect } from 'vue'

const props = defineProps<{ url: string }>()

const data = ref<T | null>(null)
const error = ref<Error | null>(null)
const loading = ref(true)

watchEffect(async () => {
  loading.value = true
  try {
    const res = await fetch(props.url)
    data.value = await res.json()
  } catch (e) {
    error.value = e as Error
  } finally {
    loading.value = false
  }
})
</script>

<template>
  <slot :data="data" :error="error" :loading="loading" />
</template>

<!-- Usage -->
<FetchData url="/api/users" v-slot="{ data: users, loading }">
  <div v-if="loading">Loading...</div>
  <ul v-else>
    <li v-for="user in users" :key="user.id">{{ user.name }}</li>
  </ul>
</FetchData>

2. Render Functions

import { h, defineComponent } from 'vue'

// Render function component
export const DynamicHeading = defineComponent({
  props: {
    level: { type: Number, required: true, validator: (v: number) => v >= 1 && v <= 6 },
  },
  setup(props, { slots }) {
    return () => h(`h${props.level}`, {
      class: 'heading',
    }, slots.default?.())
  },
})

// Functional component
export function SmartLink(props: { href: string }, { slots }: any) {
  const isExternal = props.href.startsWith('http')

  if (isExternal) {
    return h('a', {
      href: props.href,
      target: '_blank',
      rel: 'noopener noreferrer',
    }, slots.default?.())
  }

  return h(resolveComponent('NuxtLink'), { to: props.href }, slots.default?.())
}

3. Higher-Order Components

// composables/withAuth.ts
import { defineComponent, h } from 'vue'

export function withAuth(WrappedComponent: any) {
  return defineComponent({
    setup(props, { attrs, slots }) {
      const { loggedIn } = useUserSession()

      return () => {
        if (!loggedIn.value) {
          return h('div', { class: 'auth-required' }, 'Vui lòng đăng nhập')
        }
        return h(WrappedComponent, { ...props, ...attrs }, slots)
      }
    },
  })
}

// Usage
const ProtectedDashboard = withAuth(Dashboard)

4. Creating Nuxt Module

// modules/analytics/index.ts
import { defineNuxtModule, addPlugin, addServerHandler, createResolver } from '@nuxt/kit'

export interface ModuleOptions {
  trackingId: string
  debug?: boolean
}

export default defineNuxtModule<ModuleOptions>({
  meta: {
    name: 'my-analytics',
    configKey: 'analytics',
    compatibility: { nuxt: '>=3.0.0' },
  },
  defaults: {
    trackingId: '',
    debug: false,
  },
  setup(options, nuxt) {
    const resolver = createResolver(import.meta.url)

    // Validate config
    if (!options.trackingId) {
      console.warn('[analytics] trackingId is required')
      return
    }

    // Add runtime config
    nuxt.options.runtimeConfig.public.analytics = {
      trackingId: options.trackingId,
      debug: options.debug,
    }

    // Add client plugin
    addPlugin(resolver.resolve('./runtime/plugin.client'))

    // Add server route
    addServerHandler({
      route: '/api/analytics/track',
      handler: resolver.resolve('./runtime/server/track.post'),
    })

    // Add composable
    addImports({
      name: 'useAnalytics',
      from: resolver.resolve('./runtime/composables/useAnalytics'),
    })
  },
})

5. Nuxt Layers

// nuxt.config.ts — extend from layers
export default defineNuxtConfig({
  extends: [
    './layers/base',           // Local layer
    'github:org/shared-layer', // Remote layer
  ],
})

// layers/base/nuxt.config.ts
export default defineNuxtConfig({
  components: [{ path: './components', prefix: 'Base' }],
  css: ['./assets/base.css'],
})

Bài tiếp theo: Testing Vue & Nuxt — Vitest, Vue Test Utils, Playwright.