1. App.vue 在 Vue 3 项目中的基础定位
App.vue 是 Vue 3 应用的根组件,作为整个单页应用(SPA)的顶级容器组件。它负责承载所有页面内容、管理全局布局结构、处理路由视图渲染,是整个应用的"骨架"和"入口"。
与 main.ts 的关系:在 main.ts 中,App.vue 被作为根组件传递给 createApp() 函数,然后挂载到 DOM 中。这种设计使得 App.vue 成为所有其他组件的父组件,可以统一管理全局状态、样式和布局。
2. App.vue 的基本结构与核心功能
2.1 基础代码结构
一个最简单的 App.vue 文件包含以下三部分:
<template>
<!-- 模板区域 -->
<div id="app">
<router-view />
</div>
</template>
<script setup lang="ts">
// 脚本区域
</script>
<style scoped>
/* 样式区域 */
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
</style>
2.2 三个核心区域解析
template 模板区域:
- 使用 HTML 语法定义组件的 UI 结构
- 必须包含一个根元素(通常是 div)
- 支持 Vue 的模板语法(插值、指令、事件绑定等)
script 脚本区域:
- 使用 JavaScript/TypeScript 编写组件逻辑
- 支持选项式 API 或组合式 API(推荐使用
<script setup>) - 定义响应式数据、计算属性、方法等
style 样式区域:
- 使用 CSS 或预处理器(SCSS、Less)编写样式
- 支持
scoped属性实现样式作用域隔离 - 可以定义全局样式或组件级样式
3. 完整配置与功能集成
3.1 路由视图配置
App.vue 的核心作用是渲染路由视图,通常使用 <router-view> 组件:
<template>
<div id="app">
<!-- 导航栏 -->
<nav v-if="$route.meta.showNav !== false">
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
</nav>
<!-- 主内容区域 -->
<main>
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</main>
<!-- 页脚 -->
<footer v-if="$route.meta.showFooter !== false">
<p>© 2025 My App</p>
</footer>
</div>
</template>
3.2 全局布局管理
App.vue 负责定义应用的全局布局结构:
<template>
<div id="app">
<!-- 顶部导航 -->
<header class="app-header">
<div class="container">
<h1 class="logo">My App</h1>
<nav class="nav">
<router-link to="/" class="nav-link">首页</router-link>
<router-link to="/about" class="nav-link">关于</router-link>
<router-link to="/contact" class="nav-link">联系</router-link>
</nav>
</div>
</header>
<!-- 侧边栏(条件渲染) -->
<aside v-if="showSidebar" class="sidebar">
<ul>
<li v-for="item in sidebarItems" :key="item.path">
<router-link :to="item.path">{{ item.title }}</router-link>
</li>
</ul>
</aside>
<!-- 主内容区域 -->
<main :class="{ 'with-sidebar': showSidebar }">
<router-view />
</main>
<!-- 底部信息 -->
<footer class="app-footer">
<div class="container">
<p>© 2025 My App. All rights reserved.</p>
</div>
</footer>
</div>
</template>
3.3 全局状态管理
在 App.vue 中管理全局状态和逻辑:
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
// 全局状态
const isMobile = ref(false)
const isLoading = ref(false)
const user = ref(null)
// 计算属性
const showSidebar = computed(() => {
return !isMobile.value && route.meta.showSidebar !== false
})
// 响应式窗口大小
const checkIsMobile = () => {
isMobile.value = window.innerWidth < 768
}
// 生命周期钩子
onMounted(() => {
window.addEventListener('resize', checkIsMobile)
checkIsMobile()
// 初始化用户信息
loadUserInfo()
})
onUnmounted(() => {
window.removeEventListener('resize', checkIsMobile)
})
// 方法
const loadUserInfo = async () => {
try {
isLoading.value = true
// 模拟异步请求
const response = await fetch('/api/user')
user.value = await response.json()
} catch (error) {
console.error('加载用户信息失败:', error)
} finally {
isLoading.value = false
}
}
// 全局事件处理
const handleGlobalClick = (event: MouseEvent) => {
// 处理全局点击事件
console.log('全局点击:', event.target)
}
// 暴露给模板
defineExpose({
user,
isMobile
})
</script>
3.4 全局样式和主题配置
App.vue 是定义全局样式的最佳位置:
<style>
/* 全局样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
}
#app {
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 全局变量 */
:root {
--primary-color: #409eff;
--success-color: #67c23a;
--warning-color: #e6a23c;
--danger-color: #f56c6c;
--text-color: #303133;
--border-color: #dcdfe6;
--bg-color: #f5f7fa;
}
/* 全局工具类 */
.text-center { text-align: center; }
.mt-20 { margin-top: 20px; }
.mb-20 { margin-bottom: 20px; }
.p-20 { padding: 20px; }
</style>
<style scoped>
/* 组件级样式 */
#app {
background-color: var(--bg-color);
}
.app-header {
background: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 0 20px;
position: sticky;
top: 0;
z-index: 1000;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 15px;
}
.logo {
font-size: 1.5rem;
font-weight: bold;
color: var(--primary-color);
}
.nav {
display: flex;
gap: 20px;
}
.nav-link {
text-decoration: none;
color: var(--text-color);
padding: 10px 0;
position: relative;
}
.nav-link.router-link-active {
color: var(--primary-color);
}
.nav-link.router-link-active::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background-color: var(--primary-color);
}
main {
flex: 1;
padding: 20px;
}
main.with-sidebar {
margin-left: 250px;
}
.sidebar {
position: fixed;
top: 60px;
left: 0;
width: 250px;
height: calc(100vh - 60px);
background: #fff;
border-right: 1px solid var(--border-color);
padding: 20px;
overflow-y: auto;
}
.app-footer {
background: #fff;
border-top: 1px solid var(--border-color);
padding: 20px;
text-align: center;
color: #666;
}
</style>
4. 高级配置与最佳实践
4.1 全局组件注册
虽然推荐在 main.ts 中注册全局组件,但也可以在 App.vue 中按需导入:
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
// 异步加载全局组件
const GlobalLoading = defineAsyncComponent(() =>
import('@/components/GlobalLoading.vue')
)
const GlobalToast = defineAsyncComponent(() =>
import('@/components/GlobalToast.vue')
)
</script>
<template>
<div id="app">
<!-- 全局组件 -->
<GlobalLoading />
<GlobalToast />
<router-view />
</div>
</template>
4.2 错误边界处理
实现全局错误捕获:
<script setup lang="ts">
import { ref, onErrorCaptured } from 'vue'
const error = ref<Error | null>(null)
onErrorCaptured((err, instance, info) => {
error.value = err
console.error('组件错误:', err, info)
// 可以发送到错误监控服务
return false // 阻止错误继续向上传播
})
// 错误处理函数
const handleResetError = () => {
error.value = null
}
</script>
<template>
<div id="app">
<template v-if="error">
<div class="error-boundary">
<h2>应用出错了</h2>
<p>{{ error.message }}</p>
<button @click="handleResetError">重试</button>
</div>
</template>
<template v-else>
<router-view />
</template>
</div>
</template>
<style scoped>
.error-boundary {
padding: 40px;
text-align: center;
color: #f56c6c;
}
</style>
4.3 主题切换功能
实现全局主题切换:
<script setup lang="ts">
import { ref, watch, onMounted } from 'vue'
const theme = ref<'light' | 'dark'>('light')
// 切换主题
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
// 监听主题变化
watch(theme, (newTheme) => {
document.documentElement.setAttribute('data-theme', newTheme)
localStorage.setItem('theme', newTheme)
})
// 初始化主题
onMounted(() => {
const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | null
if (savedTheme) {
theme.value = savedTheme
} else {
// 根据系统偏好设置
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
theme.value = prefersDark ? 'dark' : 'light'
}
})
</script>
<template>
<div id="app" :class="theme">
<button @click="toggleTheme" class="theme-toggle">
{{ theme === 'light' ? '🌙' : '☀️' }}
</button>
<router-view />
</div>
</template>
<style>
/* 主题变量 */
:root[data-theme="light"] {
--bg-color: #ffffff;
--text-color: #333333;
--primary-color: #409eff;
}
:root[data-theme="dark"] {
--bg-color: #1a1a1a;
--text-color: #ffffff;
--primary-color: #64b5f6;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s, color 0.3s;
}
.theme-toggle {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
background: var(--primary-color);
border: none;
border-radius: 50%;
width: 40px;
height: 40px;
cursor: pointer;
font-size: 1.2rem;
}
</style>
4.4 国际化配置
集成国际化功能:
<script setup lang="ts">
import { ref, provide } from 'vue'
import { useI18n } from 'vue-i18n'
// 创建 i18n 实例
const { t, locale } = useI18n()
const currentLocale = ref('zh-CN')
// 切换语言
const changeLocale = (newLocale: string) => {
currentLocale.value = newLocale
locale.value = newLocale
}
// 提供国际化函数给子组件
provide('t', t)
</script>
<template>
<div id="app">
<div class="locale-switcher">
<button @click="changeLocale('zh-CN')" :class="{ active: currentLocale === 'zh-CN' }">
中文
</button>
<button @click="changeLocale('en-US')" :class="{ active: currentLocale === 'en-US' }">
English
</button>
</div>
<router-view />
</div>
</template>
<style scoped>
.locale-switcher {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
}
.locale-switcher button {
margin: 0 5px;
padding: 5px 10px;
border: 1px solid #ccc;
background: #fff;
cursor: pointer;
}
.locale-switcher button.active {
background: #409eff;
color: #fff;
border-color: #409eff;
}
</style>
5. 实际项目中的完整示例
以下是一个真实项目中 App.vue 的完整配置示例:
<template>
<div id="app" :class="[theme, { 'sidebar-collapsed': isSidebarCollapsed }]">
<!-- 全局加载状态 -->
<GlobalLoading v-if="isLoading" />
<!-- 错误边界 -->
<ErrorBoundary v-if="error" :error="error" @reset="handleResetError" />
<!-- 应用布局 -->
<template v-else>
<!-- 顶部导航 -->
<AppHeader
:user="user"
:is-mobile="isMobile"
:is-sidebar-collapsed="isSidebarCollapsed"
@toggle-sidebar="toggleSidebar"
@toggle-theme="toggleTheme"
/>
<!-- 侧边栏 -->
<AppSidebar
v-if="showSidebar"
:is-collapsed="isSidebarCollapsed"
:menu-items="menuItems"
@toggle="toggleSidebar"
/>
<!-- 主内容区域 -->
<main :class="{ 'with-sidebar': showSidebar }">
<!-- 面包屑导航 -->
<Breadcrumb v-if="showBreadcrumb" />
<!-- 路由视图 -->
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</main>
<!-- 页脚 -->
<AppFooter v-if="showFooter" />
<!-- 全局提示 -->
<GlobalToast />
</template>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, provide } from 'vue'
import { useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useUserStore } from '@/stores/user'
// 全局组件
import AppHeader from '@/components/layout/AppHeader.vue'
import AppSidebar from '@/components/layout/AppSidebar.vue'
import AppFooter from '@/components/layout/AppFooter.vue'
import GlobalLoading from '@/components/common/GlobalLoading.vue'
import GlobalToast from '@/components/common/GlobalToast.vue'
import Breadcrumb from '@/components/common/Breadcrumb.vue'
import ErrorBoundary from '@/components/common/ErrorBoundary.vue'
const route = useRoute()
const { t } = useI18n()
const userStore = useUserStore()
// 响应式状态
const isMobile = ref(false)
const isLoading = ref(false)
const error = ref<Error | null>(null)
const theme = ref<'light' | 'dark'>('light')
const isSidebarCollapsed = ref(false)
// 计算属性
const showSidebar = computed(() => {
return !isMobile.value && route.meta.showSidebar !== false
})
const showBreadcrumb = computed(() => {
return route.meta.showBreadcrumb !== false
})
const showFooter = computed(() => {
return route.meta.showFooter !== false
})
const menuItems = computed(() => {
return [
{ path: '/', title: t('menu.home'), icon: 'home' },
{ path: '/dashboard', title: t('menu.dashboard'), icon: 'dashboard' },
{ path: '/settings', title: t('menu.settings'), icon: 'settings' }
]
})
// 方法
const toggleSidebar = () => {
isSidebarCollapsed.value = !isSidebarCollapsed.value
}
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
document.documentElement.setAttribute('data-theme', theme.value)
localStorage.setItem('theme', theme.value)
}
const handleResetError = () => {
error.value = null
}
// 响应式窗口大小
const checkIsMobile = () => {
isMobile.value = window.innerWidth < 768
if (isMobile.value) {
isSidebarCollapsed.value = true
}
}
// 生命周期钩子
onMounted(async () => {
window.addEventListener('resize', checkIsMobile)
checkIsMobile()
// 初始化主题
const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | null
if (savedTheme) {
theme.value = savedTheme
}
// 加载用户信息
try {
isLoading.value = true
await userStore.loadUser()
} catch (err) {
error.value = err as Error
} finally {
isLoading.value = false
}
})
onUnmounted(() => {
window.removeEventListener('resize', checkIsMobile)
})
// 全局错误捕获
onErrorCaptured((err, instance, info) => {
error.value = err
console.error('组件错误:', err, info)
return false
})
// 提供全局工具
provide('app', {
isMobile,
theme,
toggleTheme
})
</script>
<style>
/* 全局样式重置和变量 */
:root {
--primary-color: #409eff;
--success-color: #67c23a;
--warning-color: #e6a23c;
--danger-color: #f56c6c;
--text-color: #303133;
--border-color: #dcdfe6;
--bg-color: #f5f7fa;
}
:root[data-theme="dark"] {
--primary-color: #64b5f6;
--text-color: #ffffff;
--bg-color: #1a1a1a;
--border-color: #333;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: var(--bg-color);
transition: background-color 0.3s, color 0.3s;
}
#app {
min-height: 100vh;
display: flex;
flex-direction: column;
}
main {
flex: 1;
padding: 20px;
}
main.with-sidebar {
margin-left: 250px;
transition: margin-left 0.3s;
}
#app.sidebar-collapsed main.with-sidebar {
margin-left: 64px;
}
/* 过渡动画 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
总结
App.vue 作为 Vue 3 应用的根组件,承担着至关重要的职责:
- 布局管理:定义全局的页面结构(头部、侧边栏、内容区、页脚)
- 状态管理:管理全局状态(主题、用户信息、加载状态等)
- 路由渲染:通过
<router-view>渲染不同页面 - 错误处理:实现全局错误边界,捕获子组件错误
- 样式管理:定义全局样式和主题变量
- 插件集成:集成国际化、状态管理等第三方库
通过合理的配置,App.vue 可以成为整个应用的"控制中心",统一管理所有全局功能和状态。掌握 App.vue 的配置技巧,能够帮助你构建更加健壮、可维护的 Vue 3 应用。
1100

被折叠的 条评论
为什么被折叠?



