第 1 节 概述
在复杂的前端项目中,会有多个页面和模块,使用 Vue 3 构建复杂项目时,为了方便实现页面的模块化解耦以及统一管理,需要使用组件和路由。
第 2 节 目录结构
一个复杂的 Vue 3 项目会由多个组件构成,这些组件组成了如下的目录结构:
2.1 配置 Tailwind CSS
在项目目录下使用命令进行安装:
1
|
npm install tailwindcss @tailwindcss/vite
|
在 vite.config.ts 中增加以下内容:
1
2
3
4
|
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()]
})
|
在 /src/styles/index.css 中引入 Tailwind CSS:
在 /src/main.ts 中增加以下内容:
1
|
import './styles/index.css'
|
2.2 配置 TypeScript
1
2
3
4
5
|
src/
├─ types/
│ ├─ xxx.ts
│ ├─ xxx.ts
│ └─ xxx.ts
|
TypeScript 的类型定义放在 /src/types 目录下,需要新增类型时,创建 xxx.ts 文件,比如创建一个 user.ts 文件:
1
2
3
4
5
6
7
|
export type gender = '男' | '女'
export interface User {
id: number
name: string
gender: gender
}
|
在 xxx.vue 中这样使用,使用 @ 符号表示项目的 src 目录:
1
2
3
4
5
6
7
8
9
|
<script lang="ts" setup>
import { RouterLink, RouterView } from 'vue-router'
import type { User } from '@/types/user'
const person: User = {
id: 1,
name: '张三',
gender: '男',
}
</script>
|
@ 符号的定义在 vite.config.ts 中, 使用 npm create vue@latest 的时候会自动创建好:
1
2
3
4
5
6
7
|
export default defineConfig({
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})
|
2.3 配置组件
1
2
3
4
5
|
src/
├─ components/
│ ├─ xxx.vue
│ ├─ xxx.vue
│ └─ xxx.vue
|
通用的小组件放在 /scr/components 目录下,文件名是 xxx.vue,Vue 3 要求组件名首字母大写,并且至少要有两个单词组成,比如,创建 SubmitButton.vue 文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<template>
<button
class="px-4 py-2 rounded bg-blue-500 text-white text-sm hover:bg-blue-600 transition"
@click="handleClick"
>
提交
</button>
</template>
<script setup lang="ts">
function handleClick() {
console.log('SubmitButton 被点击了')
}
</script>
|
在 xxx.vue 文件中,我想要使用这个组件,可以这样写:
1
2
3
4
5
6
7
|
<template>
<SubmitButton></SubmitButton>
</template>
<script lang="ts" setup>
import SubmitButton from '@/components/SubmitButton.vue'
</script>
|
2.4 配置路由
1
2
3
4
5
6
7
|
src/
├─ pages/
│ ├─ xxx.vue
│ ├─ xxx.vue
│ └─ xxx.vue
├─ router/
│ └─ index.ts
|
页面组件需要配置单独的 URL,路由(Router)就是管理页面跳转和 URL 映射的机制,所以我们需要通过路由来配置这些组件。
使用 npm create vue@latest 的时候可以选择是否配置路由,Vue 3 会自动为我们配置路由。
Vue 3 会为我们安装 vue-router,在项目目录下执行命令:
Vue 3 会创建 /src/router/index.ts 文件,并且进行初始化,导出一个 router 对象:
1
2
3
4
5
6
7
8
|
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [],
})
export default router
|
Vue 3 会在 /src/main.ts 中导入 router 并使用:
1
2
3
4
|
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
|
Vue 3 的页面组件需要放到 /src/pages 文件夹中。
vue-router 的具体使用方法在后面的章节讲述。
2.5 配置状态管理
Vue 3 中有多种组件之间的通信方式,使用 Pinia 是其中一种方法,Pinia 是一个状态管理库,可以实现多个组件共享数据,当其状态改变时,相关组件自动更新。
使用 npm create vue@latest 的时候可以选择是否配置状态管理,Vue 3 会自动为我们配置状态管理。
Vue 3 会为我们安装 pinia,在项目目录下执行命令:
Vue 3 会在 /src/main.ts 中导入 pinia 并使用:
1
2
3
4
|
import { createPinia } from 'pinia'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
|
pinia 的具体使用方法在后面的章节讲述。
第 3 节 组件通信
通常,父组件和子组件之间存在数据交互,Vue 3 中实现了这一交互,可以很方便实现组件之间的通信。
3.1 props 属性
第 4 节 路由
以一个简单的新闻网页为例说明如何构建路由,网页有导航栏和内容区域两部分,导航栏有主页、新闻和关于三个标签,点击导航栏的标签,内容区域加载对应的页面。
4.1 页面跳转
在 /src/pages 目录下创建 Home.vue、News.vue 和 About.vue 三个组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<template>
<div>
<h1 class="text-2xl font-bold">首页</h1>
<p>欢迎来到首页</p>
</div>
</template>
<template>
<div>
<h1 class="text-2xl font-bold">新闻</h1>
<p>这里是新闻页面</p>
</div>
</template>
<template>
<div>
<h1 class="text-2xl font-bold">关于</h1>
<p>这里是关于页面</p>
</div>
</template>
|
在 /src/router/index.ts 中写入如下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import About from '@/pages/About.vue'
const routes = [
{
path: '/',
redirect: '/home',
},
{
path: '/home',
component: Home,
},
{
path: '/news',
component: News,
},
{
path: '/about',
component: About,
},
]
const router = createRouter({
history: createWebHistory(),
routes,
})
export default router
|
然后在 xxx.vue 中,需要引入 RouterLink 和 RouterView:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<template>
<div>
<nav class="p-4 bg-gray-200 flex gap-10 justify-center">
<router-link class="hover:text-blue-500" to="/">首页</router-link>
<router-link class="hover:text-blue-500" to="/news">新闻</router-link>
<router-link class="hover:text-blue-500" to="/about">关于</router-link>
</nav>
<div class="p-4">
<router-view />
</div>
</div>
</template>
<script lang="ts" setup>
import { RouterLink, RouterView } from 'vue-router'
</script>
|
router-link 标签中需要指定属性 to 为路由的 path,router-view 标签为加载页面内容的位置。
4.2 路由命名
在 /src/router/index.ts 中,路由可以指定 name 属性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
const routes = [
{
path: '/',
redirect: '/home',
},
{
path: '/home',
name: 'home',
component: Home,
},
{
path: '/news',
name: 'news',
component: News,
},
{
path: '/about',
name: 'about',
component: About,
},
]
|
这样在 xxx.vue 中就可以使用 :to,通过 name 来查找 path,当 path 比较长时,这种方式会更加简便:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<template>
<div>
<nav class="p-4 bg-gray-200 flex gap-10 justify-center">
<router-link :to="{ name: 'home' }" class="hover:text-blue-500">首页</router-link>
<router-link :to="{ name: 'news' }" class="hover:text-blue-500">新闻</router-link>
<router-link :to="{ name: 'about' }" class="hover:text-blue-500">关于</router-link>
</nav>
<div class="p-4">
<router-view />
</div>
</div>
</template>
|
4.3 嵌套路由
路由可以嵌套为多级,比如新闻页面下可以有新闻的详情页面。
修改之前的 News.vue:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
<template>
<div class="flex gap-4">
<div class="w-1/4 bg-gray-100 p-4 rounded">
<h2 class="text-xl font-bold mb-2">新闻列表</h2>
<ul class="space-y-2">
<li v-for="item in newsList" :key="item.id">
<router-link
:to="{ name: 'news-detail', params: { id: item.id } }"
class="block text-blue-500 hover:underline"
>
{{ item.title }}
</router-link>
</li>
</ul>
</div>
<div class="flex-1 bg-white p-4 rounded shadow">
<router-view />
<!-- 如果没有选择新闻,显示默认提示 -->
<template>
<p class="text-gray-400">请选择一条新闻查看详情</p>
</template>
</div>
</div>
</template>
<script setup lang="ts">
const newsList = [
{ id: 1, title: '新闻 1' },
{ id: 2, title: '新闻 2' },
{ id: 3, title: '新闻 3' },
]
</script>
|
在 /src/pages 目录下创建 NewsDetail.vue:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<template>
<div>
<h2 class="text-xl font-bold">新闻详情页</h2>
<p>新闻 ID: {{ newsId }}</p>
</div>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router'
import { computed } from 'vue'
const route = useRoute()
const newsId = computed(() => route.params.id)
</script>
|
修改/src/router/index.ts:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
const routes = [
{
path: '/',
redirect: { name: 'home' },
},
{
path: '/home',
name: 'home',
component: Home,
},
{
path: '/news',
name: 'news',
component: News,
children: [
{
path: ':id',
name: 'news-detail',
component: NewsDetail,
},
],
},
{
path: '/about',
name: 'about',
component: About,
},
]
|
在 /news 下新增 children 属性,并定义 path 为 :id, 这样新闻详情的 path 就会是多级嵌套的叠加,变成 /news/:id,这里的 :id 为动态参数,在 news.vue 中传入。
4.4 路由传参
路由的传参方式有两种,一种是使用 query 传参,一种是使用 params 传参,上面的例子中使用的就是 params 传参,稍作修改就可以变成使用 query 传参。
在/src/pages/NewsDetail.vue中稍作修改:
1
2
3
4
5
6
|
<router-link
:to="{ name: 'news-detail', query: { id: item.id } }"
class="block text-blue-500 hover:underline"
>
{{ item.title }}
</router-link>
|
然后修改/src/router/index.ts:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
const routes = [
{
path: '/news',
name: 'news',
component: News,
children: [
{
path: '',
name: 'news-detail',
component: NewsDetail,
},
],
}
]
|
使用query传参时,path形式类似/news?id=1&x=x&y=y,使用params传参时path形式类似/news/1/x/y,可以看到两种传参方式差别不是很大。