第 3 章:组件与布局
第 3 章:组件与布局(复用 UI 与统一结构)
本章讲解 Nuxt 4 组件机制与布局系统,结合完整代码 + 实战演练,完成导航栏、内容卡片、多布局等通用模块封装,适配掘金小册平台页面复用需求。
3.1 组件自动导入:components 目录与全局注册
Nuxt 4 基于约定式目录,app/components/ 下的组件无需手动 import,全局自动导入,直接在模板中使用。
目录规则
- 1,一级组件:components/BookCard.vue → 直接使用
- 2,子目录组件:components/Header/Nav.vue → 自动转为 (目录名 + 组件名)
实战代码
新建全局组件 app/components/Hello.vue
在任意页面直接使用,无需引入:<template> <div class="demo-component"> <h3>Nuxt 4 自动导入组件 Hello</h3> </div> </template>
app/pages/index.vue<template> <div> <h1>网站首页</h1> <!-- 直接使用组件 --> <Hello /> </div> </template>实战演练
- 1,在新建 Tips.vue 组件,编写简单提示文本
- 2,在首页、小册列表页分别引用该组件
- 3,运行项目,验证无需 import 即可正常渲染
3.2 组合式组件:defineProps / defineEmits / defineExpose
在 Vue3 + Nuxt4 的 <script setup> 语法中,使用内置 API 实现父子组件传值、事件派发、实例暴露,是业务组件开发基础。
1. defineProps 父传子
子组件 app/components/BookItem.vue
<template>
<div class="book-item">
<p>小册名称:{{ title }}</p>
<p>阅读量:{{ viewCount }}</p>
</div>
</template>
<script setup>
// 定义接收父组件参数
const props = defineProps({
title: String,
viewCount: Number
})
</script>
父组件使用并传参:
<template>
<div>
<BookItem title="Nuxt4 实战教程" :view-count="1200" />
</div>
</template>
2. defineEmits 子传父(触发自定义事件)
改造子组件,添加点击派发事件:
子组件 app/components/BookItem2.vue
<template>
<div class="book-item2" @click="handleClick">
<p>小册名称:{{ title }}</p>
</div>
</template>
<script setup>
const props = defineProps(['title'])
// 定义自定义事件
const emit = defineEmits(['select'])
const handleClick = () => {
// 向父组件派发事件并传参
emit('select', props.title)
}
</script>
父组件监听事件:
<template>
<BookItem2 title="Vue3 入门1" @select="onBookSelect" />
<BookItem2 title="Vue2 KK" @select="onBookSelect" />
</template>
<script setup>
const onBookSelect = (name) => {
console.log('选中小册:', name)
}
</script>
3. defineExpose 暴露组件实例与方法
子组件向外暴露内部属性 / 方法,供父组件通过 ref 调用:
子组件增加 app/components/BookItem.vue
<script setup>
const msg = '组件内部数据'
const showInfo = () => {
alert('组件方法被调用')
}
// 对外暴露
defineExpose({
msg,
showInfo
})
</script>
父组件调用:
父组件 app/pages/index.vue 追加如下内容
<template>
<BookItem title="对外暴露数据和方法" :viewCount="1000000" ref="bookRef" />
<button @click="callChildMethod">调用子组件方法</button>
</template>
<script setup>
import { ref } from 'vue'
const bookRef = ref(null)
const callChildMethod = () => {
bookRef.value.showInfo()
console.log(bookRef.value.msg)
}
</script>
实战演练
- 1,封装小册条目组件,使用 defineProps 接收名称、封面、价格
- 2,绑定点击事件,通过 defineEmits 向上传递选中事件
- 3,使用 defineExpose 暴露一个重置方法,父组件点击触发
3.3 布局系统:layouts 目录与默认布局 default.vue
Nuxt4 布局用于统一页面公共头部、底部、侧边栏,所有页面默认继承 layouts/default.vue。
规则说明
- 目录:app/layouts/
- default.vue:全局默认布局,所有页面默认使用
- 布局出口:必须使用 <slot /> 承载页面主体内容
实战代码
创建默认布局
app/layouts/default.vue
<template>
<!-- 公共导航栏 -->
<header style="padding: 1rem; background: #f5f5f5;">
<h2>diibook小册平台 - 公共导航</h2>
</header>
<!-- 页面内容插槽 -->
<main style="padding: 2rem;">
<slot />
</main>
<!-- 公共底部 -->
<footer style="text-align: center; padding: 1rem; border-top: 1px solid #eee;">
版权所有 © 小册实战项目
</footer>
</template>
修改入口文件
app/app.vue
<template>
<div>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</div>
</template>
任意页面无需额外配置,自动套用该布局。
实战演练
- 1,编写默认布局,添加导航、主体插槽、页脚
- 2,启动项目,访问首页、列表页,查看所有页面统一套用布局
3.4 多布局切换:definePageMeta({ layout: ‘xxx’ })
项目中常存在前台布局、后台布局、登录页独立布局,通过 definePageMeta 快速切换布局。
1. 新建自定义布局
创建后台布局
app/layouts/admin.vue
<template>
<div style="display: flex;">
<!-- 后台侧边栏 -->
<aside style="width: 200px; background: #222; color: #fff; padding: 1rem;">
<p>创作者后台菜单</p>
</aside>
<!-- 后台内容区 -->
<div style="flex: 1; padding: 2rem;">
<slot />
</div>
</div>
</template>
2. 页面指定布局
后台页面 app/pages/admin/index.vue,通过路由元信息切换布局:
<template>
<div>创作者控制台</div>
</template>
<script setup>
// 指定使用 admin 布局
definePageMeta({
layout: 'admin'
})
</script>
3. 禁用布局(独立页面)
登录页、空白页可关闭所有布局,设置 layout: false:
<script setup>
definePageMeta({
layout: false
})
</script>
实战演练
- 1,新建 admin 后台布局、login 登录布局
- 2,分别给后台页、登录页绑定对应布局
- 3,访问不同页面,验证布局切换效果
3.5 全局公共组件封装(导航栏、侧边栏、卡片)
结合自动导入特性,封装掘金小册平台高频复用组件。
1. 导航栏组件 app/components/GlobalNav.vue
<template>
<nav style="background: #1677ff; padding: 0.8rem; color: #fff;">
<NuxtLink to="/" style="margin-right: 1rem; color: #fff;">首页</NuxtLink>
<NuxtLink to="/books" style="margin-right: 1rem; color: #fff;">小册列表</NuxtLink>
<NuxtLink to="/admin" style="color: #fff;">创作者中心</NuxtLink>
</nav>
</template>
2. 小册卡片组件 app/components/BookCard.vue
<template>
<div style="border: 1px solid #eee; padding: 1rem; border-radius: 6px; width: 240px;">
<h4>{{ bookName }}</h4>
<p>作者:{{ author }}</p>
<p>阅读量:{{ views }}</p>
<NuxtLink :to="`/books/${bookId}`">查看详情</NuxtLink>
</div>
</template>
<script setup>
defineProps({
bookId: [String, Number],
bookName: String,
author: String,
views: Number
})
</script>
3. 在默认布局中引入公共组件
修改 layouts/default.vue,嵌入全局导航:
<template>
<!-- 全局导航 -->
<GlobalNav />
<main style="padding: 2rem;">
<slot />
</main>
<footer>版权所有 © 小册实战项目</footer>
</template>
4. 页面批量使用卡片组件
app/pages/books/index.vue
<template>
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<BookCard book-id="1" book-name="Nuxt4 全栈实战" author="技术作者" :views="3600" />
<BookCard book-id="2" book-name="Vue3 进阶指南" author="前端达人" :views="2800" />
</div>
</template>
实战演练
- 1,封装全局导航、小册卡片两个通用组件
- 2,将导航嵌入默认布局,实现全站统一导航
- 3,在列表页循环使用卡片组件展示多条小册数据
3.6 组件懒加载与性能优化
对于大体积组件、弹窗、非首屏组件,使用懒加载减少首屏资源体积,提升页面加载速度。
Nuxt4 已经把组件懒加载、懒水合、数据懒加载做成内置能力,不需要手动写 defineAsyncComponent 也能实现 “点击再加载弹窗” 这类需求Nuxt。
1. 组件动态懒加载(局部懒加载)
Nuxt4 自动扫描 components/ 下的组件,名字加 Lazy 就自动变成异步懒加载组件 Nuxt
弹窗场景最佳实践:
app/components/Modal.vue
<template>
<div class="mask" @click.self="$emit('close')">
<div class="box">
<slot />
<button @click="$emit('close')">关闭</button>
</div>
</div>
</template>
<style scoped>
.mask { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; }
.box { background: white; padding: 24px; border-radius: 8px; }
</style>
页面使用(内置懒加载)
app/pages/books/index.vue
<template>
<button @click="showModal = true">打开弹窗</button>
<LazyModal v-if="showModal" @close="showModal = false">
我是懒加载弹窗内容
</LazyModal>
</template>
<script setup>
const showModal = ref(false)
</script>
2. 懒加载组件懒加载 + 四种水合策略 + 图片懒加载 + 数据懒加载
基础组件
app/components/HeavyTable.vue
<!-- 重型表格组件 -->
<template>
<div class="table-box">
<h3>大数据表格(懒加载组件)</h3>
<table border="1" cellpadding="6">
<tbody>
<tr>
<td>序号</td>
<td>名称</td>
<td>状态</td>
</tr>
<tr v-for="i in 10" :key="i">
<td>{{ i }}</td>
<td>测试数据 {{ i }}</td>
<td>正常</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup lang="ts">
// 模拟复杂逻辑
console.log('HeavyTable 组件已加载 & 水合 ')
</script>
components/Modal.vue
<template>
<div class="mask" @click.self="$emit('close')">
<div class="box">
<slot />
<p>点击后才完成加载与水合</p>
<button @click="$emit('close')">关闭</button>
</div>
</div>
</template>
<script setup lang="ts">
alert('Modal 已水合')
</script>
<style scoped>
.mask { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; }
.box { background: white; padding: 24px; border-radius: 8px; }
</style>
components/FooterInfo.vue
<!-- 页脚(浏览器空闲时水合) -->
<template>
<div class="footer">
网站底部信息 · 懒加载+空闲水合
</div>
</template>
<script setup lang="ts">
console.log('FooterInfo 空闲时完成水合')
</script>
<style scoped>
.footer {
margin-top: 800px;
padding: 20px;
background: #eee;
}
</style>
加入nuxt/image组件
yarn add @nuxt/image启用(nuxt.config.ts)
import { defineConfig } from 'nuxt/app'
export default defineConfig({
modules: [
‘@nuxt/image’
]
})
页面调用(内置懒加载)
app/pages/index.vue
```js
<template>
<div>
<h2>Nuxt4 内置懒加载 实战演示</h2>
<!-- 1. 可见时水合:进入视口才加载+激活(长列表/表格首选) -->
<div style="margin-top: 600px;">
<LazyHeavyTable hydrate-on-visible />
</div>
<!-- 2. 交互时水合:点击/hover 才加载激活(弹窗/表单) -->
<button @click="showModal = true">打开弹窗</button>
<LazyModal v-if="showModal" @close="showModal = false">
我是懒加载弹窗内容
</LazyModal>
<!-- 3. 浏览器空闲时水合:非关键组件 -->
<LazyFooterInfo hydrate-on-idle />
<!-- 4. 图片懒加载(Nuxt 内置 <NuxtImg> 自动懒加载) -->
<div style="margin-top: 300px;">
<h3>图片懒加载</h3>
<NuxtImg
src="images/demo.png"
width="600"
height="300"
alt="示例图"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
// 控制弹窗显示
const showModal = ref(false)
</script>
3. 基础优化建议
- 首屏非必需组件全部使用懒加载
- 大图片、富文本编辑器、表格等重型组件优先异步引入
- 拆分巨型组件,按功能拆分为多个小组件
4. 实战演练
- 1,封装一个弹窗组件,实现点击后懒加载
- 2,查看浏览器网络面板,验证组件仅在触发后才请求资源
- 3,对后台复杂组件配置全局懒加载
本章综合实战任务
- 1,完善全局导航、页脚,整合进默认布局,前台页面统一样式结构
- 2,封装可复用的小册卡片组件,实现参数传值与跳转
- 3,搭建独立后台布局,区分前台 / 后台页面样式
- 4,对弹窗、富文本等重型组件实现懒加载,优化首屏性能
- 5,熟练使用 defineProps / defineEmits 完成组件交互
本章总结
- 1,Nuxt4 components 目录组件自动全局导入,简化开发;
- 2,掌握 defineProps / defineEmits / defineExpose 实现组件通信;
- 3, layouts 布局实现全站结构统一,default.vue 为默认布局;
- 4,通过 definePageMeta 灵活切换多套布局,适配前台 / 后台 / 登录页;
- 5,封装导航、卡片等公共组件,提升代码复用率;
- 6,使用异步组件实现懒加载,完成页面性能基础优化。
nuxt4实战教程
关于 LearnKu
推荐文章: