Nuxt.js组件基础 #
一、组件概述 #
Nuxt.js 的组件系统基于 Vue.js,并提供了自动导入功能。在 components/ 目录下创建的组件会自动注册,无需手动导入。
二、创建组件 #
2.1 基本组件 #
components/AppButton.vue:
vue
<template>
<button :class="['btn', `btn-${variant}`]" :disabled="disabled">
<slot />
</button>
</template>
<script setup lang="ts">
interface Props {
variant?: 'primary' | 'secondary' | 'danger'
disabled?: boolean
}
withDefaults(defineProps<Props>(), {
variant: 'primary',
disabled: false
})
</script>
<style scoped>
.btn {
padding: 0.5rem 1rem;
border-radius: 4px;
border: none;
cursor: pointer;
font-size: 1rem;
transition: all 0.2s;
}
.btn-primary {
background: #3498db;
color: white;
}
.btn-secondary {
background: #95a5a6;
color: white;
}
.btn-danger {
background: #e74c3c;
color: white;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
2.2 使用组件 #
vue
<template>
<div>
<AppButton>默认按钮</AppButton>
<AppButton variant="secondary">次要按钮</AppButton>
<AppButton variant="danger">危险按钮</AppButton>
</div>
</template>
三、自动导入机制 #
3.1 命名规则 #
Nuxt.js 根据目录结构自动生成组件名称:
text
components/
├── AppButton.vue → <AppButton />
├── AppHeader.vue → <AppHeader />
├── blog/
│ ├── Card.vue → <BlogCard />
│ └── List.vue → <BlogList />
└── ui/
├── Button.vue → <UiButton />
└── Input.vue → <UiInput />
3.2 目录前缀 #
目录名作为组件名前缀:
components/blog/Card.vue→BlogCardcomponents/ui/Button.vue→UiButton
3.3 禁用自动导入前缀 #
在 nuxt.config.ts 中配置:
typescript
export default defineNuxtConfig({
components: [
{
path: '~/components',
pathPrefix: false
}
]
})
配置后:
components/blog/Card.vue→Card
3.4 自定义组件目录 #
typescript
export default defineNuxtConfig({
components: [
'~/components',
{
path: '~/shared/components',
prefix: 'Shared',
global: true
}
]
})
四、组件Props #
4.1 定义Props #
vue
<script setup lang="ts">
interface Props {
title: string
count?: number
items: string[]
config: {
theme: string
size: 'small' | 'medium' | 'large'
}
}
const props = withDefaults(defineProps<Props>(), {
count: 0
})
</script>
4.2 Props验证 #
vue
<script setup lang="ts">
const props = defineProps({
title: {
type: String,
required: true
},
count: {
type: Number,
default: 0,
validator: (value: number) => value >= 0
},
status: {
type: String as PropType<'active' | 'inactive'>,
default: 'active'
}
})
</script>
4.3 使用Props #
vue
<template>
<UserCard
title="用户信息"
:count="10"
:items="['item1', 'item2']"
:config="{ theme: 'dark', size: 'medium' }"
/>
</template>
五、组件事件 #
5.1 定义事件 #
vue
<script setup lang="ts">
interface Emits {
(e: 'update', value: string): void
(e: 'delete', id: number): void
(e: 'change', event: Event): void
}
const emit = defineEmits<Emits>()
const handleClick = () => {
emit('update', 'new value')
}
</script>
5.2 使用v-model #
vue
<script setup lang="ts">
interface Props {
modelValue: string
}
interface Emits {
(e: 'update:modelValue', value: string): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const updateValue = (event: Event) => {
const target = event.target as HTMLInputElement
emit('update:modelValue', target.value)
}
</script>
<template>
<input
:value="modelValue"
@input="updateValue"
/>
</template>
5.3 多个v-model #
vue
<script setup lang="ts">
interface Props {
firstName: string
lastName: string
}
interface Emits {
(e: 'update:firstName', value: string): void
(e: 'update:lastName', value: string): void
}
defineProps<Props>()
const emit = defineEmits<Emits>()
</script>
<template>
<input
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</template>
六、组件插槽 #
6.1 默认插槽 #
vue
<template>
<div class="card">
<slot />
</div>
</template>
使用:
vue
<template>
<AppCard>
<p>卡片内容</p>
</AppCard>
</template>
6.2 具名插槽 #
vue
<template>
<div class="card">
<header class="card-header">
<slot name="header" />
</header>
<main class="card-body">
<slot />
</main>
<footer class="card-footer">
<slot name="footer" />
</footer>
</div>
</template>
使用:
vue
<template>
<AppCard>
<template #header>
<h2>标题</h2>
</template>
<p>内容</p>
<template #footer>
<button>操作</button>
</template>
</AppCard>
</template>
6.3 作用域插槽 #
vue
<script setup lang="ts">
interface Item {
id: number
name: string
}
const items: Item[] = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
]
</script>
<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item" :index="item.id">
{{ item.name }}
</slot>
</li>
</ul>
</template>
使用:
vue
<template>
<ItemList>
<template #default="{ item, index }">
<span>{{ index }}: {{ item.name }}</span>
</template>
</ItemList>
</template>
七、动态组件 #
7.1 使用component标签 #
vue
<script setup lang="ts">
import { resolveComponent } from 'vue'
const currentComponent = ref('AppButton')
const components = {
AppButton: resolveComponent('AppButton'),
AppInput: resolveComponent('AppInput')
}
</script>
<template>
<component :is="components[currentComponent]" />
</template>
7.2 动态组件切换 #
vue
<script setup lang="ts">
const tabs = ['Home', 'Profile', 'Settings']
const activeTab = ref('Home')
</script>
<template>
<div>
<button
v-for="tab in tabs"
:key="tab"
@click="activeTab = tab"
>
{{ tab }}
</button>
<component :is="`${activeTab}Tab`" />
</div>
</template>
八、异步组件 #
8.1 懒加载组件 #
Nuxt.js 提供了 Lazy 前缀来懒加载组件:
vue
<template>
<div>
<LazyHeavyComponent v-if="showHeavy" />
<button @click="showHeavy = true">加载组件</button>
</div>
</template>
<script setup lang="ts">
const showHeavy = ref(false)
</script>
8.2 懒加载带loading #
vue
<template>
<div>
<LazyHeavyComponent v-if="showHeavy">
<template #fallback>
<div>加载中...</div>
</template>
</LazyHeavyComponent>
</div>
</template>
8.3 手动导入 #
vue
<script setup lang="ts">
const HeavyComponent = defineAsyncComponent(() =>
import('~/components/HeavyComponent.vue')
)
</script>
九、组件最佳实践 #
9.1 组件分类 #
text
components/
├── base/ # 基础组件
│ ├── Button.vue
│ ├── Input.vue
│ └── Icon.vue
├── layout/ # 布局组件
│ ├── Header.vue
│ ├── Footer.vue
│ └── Sidebar.vue
├── features/ # 功能组件
│ ├── SearchBar.vue
│ └── UserMenu.vue
└── ui/ # UI组件
├── Modal.vue
├── Toast.vue
└── Dropdown.vue
9.2 单文件组件结构 #
vue
<template>
<div class="component">
</div>
</template>
<script setup lang="ts">
interface Props {}
interface Emits {}
defineProps<Props>()
defineEmits<Emits>()
</script>
<style scoped>
.component {
}
</style>
9.3 组件文档 #
vue
<script setup lang="ts">
interface Props {
variant?: 'primary' | 'secondary'
size?: 'small' | 'medium' | 'large'
disabled?: boolean
}
withDefaults(defineProps<Props>(), {
variant: 'primary',
size: 'medium',
disabled: false
})
</script>
<template>
<button :disabled="disabled">
<slot />
</button>
</template>
十、完整示例 #
10.1 数据表格组件 #
components/DataTable.vue:
vue
<template>
<div class="data-table">
<table>
<thead>
<tr>
<th
v-for="column in columns"
:key="column.key"
@click="column.sortable && handleSort(column.key)"
>
{{ column.label }}
<span v-if="column.sortable && sortKey === column.key">
{{ sortOrder === 'asc' ? '↑' : '↓' }}
</span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in sortedData" :key="index">
<td v-for="column in columns" :key="column.key">
<slot
:name="`cell-${column.key}`"
:row="row"
:value="row[column.key]"
>
{{ row[column.key] }}
</slot>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup lang="ts">
interface Column {
key: string
label: string
sortable?: boolean
}
interface Props {
columns: Column[]
data: Record<string, any>[]
}
const props = defineProps<Props>()
const sortKey = ref<string | null>(null)
const sortOrder = ref<'asc' | 'desc'>('asc')
const handleSort = (key: string) => {
if (sortKey.value === key) {
sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
} else {
sortKey.value = key
sortOrder.value = 'asc'
}
}
const sortedData = computed(() => {
if (!sortKey.value) return props.data
return [...props.data].sort((a, b) => {
const aVal = a[sortKey.value!]
const bVal = b[sortKey.value!]
if (aVal < bVal) return sortOrder.value === 'asc' ? -1 : 1
if (aVal > bVal) return sortOrder.value === 'asc' ? 1 : -1
return 0
})
})
</script>
<style scoped>
.data-table table {
width: 100%;
border-collapse: collapse;
}
.data-table th,
.data-table td {
padding: 0.75rem;
border: 1px solid #e2e8f0;
text-align: left;
}
.data-table th {
background: #f7fafc;
cursor: pointer;
}
</style>
使用:
vue
<template>
<DataTable :columns="columns" :data="users">
<template #cell-actions="{ row }">
<button @click="editUser(row)">编辑</button>
<button @click="deleteUser(row)">删除</button>
</template>
</DataTable>
</template>
<script setup lang="ts">
const columns = [
{ key: 'name', label: '姓名', sortable: true },
{ key: 'email', label: '邮箱', sortable: true },
{ key: 'role', label: '角色' },
{ key: 'actions', label: '操作' }
]
const users = ref([
{ name: '张三', email: 'zhang@example.com', role: '管理员' },
{ name: '李四', email: 'li@example.com', role: '用户' }
])
</script>
十一、总结 #
本章介绍了 Nuxt.js 组件基础:
- 组件自动导入机制
- 定义和使用 Props
- 组件事件和 v-model
- 插槽的使用
- 动态组件和异步组件
- 组件最佳实践
组件是构建应用的基本单元,下一章我们将学习组件通信。
最后更新:2026-03-28