Vue Router

路由概述

前端路由是一种在单页应用(SPA)中管理页面导航的技术。其主要作用是根据用户的操作(如点击链接)动态地加载和显示不同的页面内容,而无需重新加载整个页面。

Vue 中的路由属于前端路由,它有两种常见的模式,分别是 Hash 模式和 History 模式。

Hash 模式的前端路由通过 URL 中从 # 号开始的部分实现不同组件之间的切换,# 号表示 Hash 符,# 号后面的值称为 Hash 值,该值将用于进行路由匹配。

History 模式的 URL 地址与后端路由的 URL 地址的风格一致,可以通过 URL 地址中的路径进行路由匹配。

Vue Router 初体验

Vue Router 是 Vue 官方推出的路由管理器,主要用于管理 URL,实现 URL 和组件的对应,通过 URL 进行组件之间的切换。

Vue Router 3.x 文档:https://v3.router.vuejs.org/zh/

在 Vue CLI 项目中使用 Vue Router,推荐在 GUI 中为项目安装 @vue/cli-plugin-router 插件。在创建 Vue CLI 项目时,也可以勾选 Vue Router 插件。


示例:安装 Vue Router

终端执行:

vue ui

在 GUI 中,为项目添加 @vue/cli-plugin-router 插件:

示例效果:

在 Vue CLI 项目中添加了 @vue/cli-plugin-router 插件后,在 IDEA 中查看项目结构的变化。

src/views:用于存放 Vue 视图组件。所有的 Vue 视图组件本质上都是 Vue 普通组件。

<template>
    <!-- ... -->
</template>

<script>
export default {
    // ...
}
</script>

<style scoped>

</style>

src/router/index.js:Vue Router 路由插件的配置文件,该文件的代码发生变化。

// 导入Vue、Vue Router
import Vue from 'vue'
import VueRouter from 'vue-router'

// 导入了HomeView组件
import HomeView from '../views/HomeView.vue'

// 对于脚手架项目,需要手动调用Vue.use安装路由插件
Vue.use(VueRouter)

// 配置一组路由
const routes = [
    {
        path: '/',  // 请求路径
        name: 'home', // 路由对象名称
        component: HomeView // 路由对应的组件对象
    },
    {
        path: '/about',
        name: 'about',
        // 使用函数式写法,实现路由懒加载
        // 当路由被触发时,该组件才会被异步加载
        component: () => import('../views/AboutView.vue')
    }
]

// 实例化路由管理者
const router = new VueRouter({
    mode: 'hash',  // 设置路由模式,默认为hash,可选hash/history
    routes
})

// 对外抛出路由管理者
export default router

src/main.js:项目的入口 JavaScript 文件。

// ...

// 导入路由配置文件
import router from './router'

new Vue({
  // 将路由管理者交给Vue实例
  router,
  render: h => h(App)
}).$mount('#app')

了解了项目结构的变化后,可以尝试使用 Vue Router 实现路由跳转。


示例:Vue Router 初体验

首页组件(src/views/HomeIndex.vue):

<script>
export default {
    name: "HomeIndex"
}
</script>

<template>
    <div>
        <h3>首页</h3>
        <p>欢迎访问多仔的首页...</p>
    </div>
</template>

<style scoped>

</style>

关于我们组件(src/views/AboutUs.vue):

<script>
export default {
    name: "AboutUs"
}
</script>

<template>
    <div>
        <h3>关于我们</h3>
        <p>多仔你是我的神...</p>
    </div>
</template>

<style scoped>

</style>

路由配置文件(src/router/index.js):

const routes = [
    {
        path: '/',
        name: 'HomeIndex',
        component: () => import('../views/HomeIndex.vue')
    },
    {
        path: '/about',
        name: 'AboutUs',
        component: () => import('../views/AboutUs.vue')
    }
]

根组件(src/App.vue):

<template>
    <div>
        <!--
            <router-link>:定义路由跳转链接,相当于a标签
            to:路由跳转地址,相当于a标签的href属性
            tag:将<router-link>标签渲染成什么标签,默认是a标签
        -->
        <router-link to="/" tag="button">首页</router-link>
        <router-link to="/about" tag="button">关于我们</router-link>

        <!--
            <router-view>:路由视图占位符,渲染对应的路由组件
        -->
        <router-view></router-view>
    </div>
</template>

示例效果:


嵌套路由

嵌套路由就是在一个路由中嵌套一组子路由,嵌套路由主要由页面结构决定,实际项目中的应用界面通常由多层嵌套的组件组合而成,为了使多层嵌套的组件能够通过路由访问,路由也需要具有嵌套关系,也就是在路由里面嵌套它的子路由。


示例:嵌套路由

联系我们组件(src/views/ContactUs.vue):

<script>
export default {
    name: "ContactUs"
}
</script>

<template>
    <div>
        <h3>联系我们</h3>
        <p>duozai.cn</p>
    </div>
</template>

<style scoped>

</style>

资质荣誉组件(src/views/HonorUs.vue):

<script>
export default {
    name: "HonorUs"
}
</script>

<template>
    <div>
        <h3>资质荣誉</h3>
        <p>曾经获得码神称号...</p>
    </div>
</template>

<style scoped>

</style>

路由配置文件(src/router/index.js):

const routes = [
    // ...
    {
        path: '/about',
        name: 'AboutUs',
        component: () => import('../views/AboutUs.vue'),
        children: [
            // 子路由列表
            // 当使用children属性定义子路由匹配规则时,子路由的path属性前不要带/
            // 否则会永远以根路径开始请求
            // 子路由地址 = 父路由 + "/" + 子路由
            
            {
                path: 'contact',
                name: 'ContactUs',
                component: () => import('../views/ContactUs.vue')
            },
            {
                path: 'honor',
                name: 'HonorUs',
                component: () => import('../views/HonorUs.vue')
            }
        ]
    }
]

关于我们组件(src/views/AboutUs.vue):

<template>
    <div>
        <h3>关于我们</h3>
        <p>多仔你是我的神...</p>

        <!--
            定义子路由跳转链接和子路由视图占位符
        -->
        <router-link to="/about/contact">联系我们</router-link>
        <router-link to="/about/honor">资质荣誉</router-link>
        <router-view></router-view>
    </div>
</template>

示例效果:


命名路由

Vue-Router 提供了一种隐式的引用路径,即命名路由,在创建路由时给路由指定 name 名称,链接路由跳转时可以通过路由的名称取代路由地址。


示例:命名路由

根组件(src/App.vue):

<template>
    <div>
        <!-- ... -->

        <!--
            :to:绑定要跳转的路由对象
            name:和路由的name一致
        -->
        <router-link :to="{name: 'AboutUs'}" tag="button">关于我们(通过命名路由跳转)</router-link>
        <router-view></router-view>
    </div>
</template>

示例效果:


路由传参

Vue-Router 在实现路由跳转时,可以传递参数。

使用 query 传参,类似于 GET 请求,参数在地址栏可见,页面刷新后数据不丢失。


示例:query 传参

根组件(src/App.vue):

<template>
    <div>
        <router-link to="/" tag="button">首页</router-link>

        <!--
            query传参:类似于GET请求
            直接在URL上拼接查询字符串
        -->
        <router-link to="/about?id=7&name=duozai" tag="button">关于我们</router-link>
        <router-view></router-view>
    </div>
</template>

关于我们组件(src/views/AboutUs.vue):

<template>
    <div>
        <h3>关于我们</h3>
        <p>多仔你是我的神...</p>
        <!--
            获取参数:this.$route.query.xxx
        -->
        <p>传递的id参数:{{this.$route.query.id}}</p>
        <p>传递的name参数:{{this.$route.query.name}}</p>
    </div>
</template>

示例效果:


使用 name + params 传参,类似于 POST 请求,参数在地址栏不可见,页面刷新后数据丢失。


示例:name + params 传参

根组件(src/App.vue):

<template>
    <div>
        <router-link to="/" tag="button">首页</router-link>

        <!--
            params传参:类似于POST请求
            只能通过name+params进行传递,不能使用路径跳转传递
        -->
        <router-link :to="{
            name: 'AboutUs',
            params: {
                id: 1,
                name: '张三'
            }
        }" tag="button">关于我们</router-link>
        <router-view></router-view>
    </div>
</template>

关于我们组件(src/views/AboutUs.vue):

<template>
    <div>
        <h3>关于我们</h3>
        <p>多仔你是我的神...</p>
        <!--
            获取参数:this.$route.params.xxx
        -->
        <p>传递的id参数:{{this.$route.params.id}}</p>
        <p>传递的name参数:{{this.$route.params.name}}</p>
    </div>
</template>

示例效果:


使用路由占位符 + params 传参,类似于 RESTFul 风格。


示例:路由占位符 + params 传参

路由配置文件(src/router/index.js):

const routes = [
    // ...
    {
        // 定义路由占位符:xxx  (动态路由)
        // 路由格式:/about/2/李四
        path: '/about/:id/:name',
        name: 'AboutUs',
        component: () => import('../views/AboutUs.vue')
    }
]

根组件(src/App.vue):

<template>
    <div>
        <router-link to="/" tag="button">首页</router-link>

        <!--
            路由占位符+params传参:类似于RESTFul风格
        -->
        <router-link to="/about/2/李四" tag="button">关于我们</router-link>
        <router-view></router-view>
    </div>
</template>

关于我们组件(src/views/AboutUs.vue):

<template>
    <div>
        <h3>关于我们</h3>
        <p>多仔你是我的神...</p>
        <!--
            获取参数:this.$route.params.xxx
        -->
        <p>传递的id参数:{{this.$route.params.id}}</p>
        <p>传递的name参数:{{this.$route.params.name}}</p>
    </div>
</template>

示例效果:


编程式导航

在 Vue 中,页面有两种导航方式:

  • 声明式导航:使用 标签定义导航链接的方式。
  • 编程式导航:使用全局路由实例的方法 $router.xxx() 实现导航链接的方式。

编程式导航的基本用法:

// 路由跳转
$router.push(跳转路径或跳转路由对象)

// 路由替换
$router.replace(跳转路径或跳转路由对象)

// 路由前进和后退
$router.go(n)

示例:编程式导航

根组件(src/App.vue):

<template>
    <div>
        <router-link to="/" tag="button">首页</router-link>
        <button @click="$router.push('/about/2/李四')">关于我们</button>
        <router-view></router-view>
    </div>
</template>

示例效果:


Vuex

Vuex 概述

Vuex 是一个专为 Vue 开发的状态(即数据)管理库,它采用集中式存储的方式管理应用的所有组件的状态,解决了多组件数据通信的问题,使数据操作更加简洁。

Vuex 3.x 官方文档:https://v3.vuex.vuejs.org/zh/

Vuex 初体验

在 Vue CLI 项目中使用 Vuex,推荐在 GUI 中为项目安装 @vue/cli-plugin-vuex 插件。在创建 Vue CLI 项目时,也可以勾选 Vuex 功能。


示例:安装 Vuex

终端执行:

vue ui

在 GUI 中,为项目添加 @vue/cli-plugin-vuex 插件:


在 Vue CLI 项目中添加了 @vue/cli-plugin-vuex 插件后,在 IDEA 中查看项目结构的变化。

src/store/index.js:Vuex 状态管理插件的配置文件。

// 导入Vue、Vuex
import Vue from 'vue'
import Vuex from 'vuex'

// 手动安装Vuex插件
Vue.use(Vuex)

// 实例化Vuex.Store即Vuex的数据仓库
// 并将其导出
export default new Vuex.Store({
    state: {
        // state:用于存放状态即数据
    },
    getters: {
        // getters:用于计算属性
    },
    mutations: {
        // mutations:用于修改state中的数据
    },
    actions: {
        // actions:用于处理异步操作
    },
    modules: {
        // modules:用于将数据仓库拆分为多个模块
    }
})

src/main.js:项目的入口 JavaScript 文件,该文件的代码发生变化。

// 导入Vuex的配置文件
import store from './store'

new Vue({
  router,
  // 将Vuex数据仓库交给Vue实例
  store,
  render: h => h(App)
}).$mount('#app')

了解了项目结构的变化后,可以尝试使用 Vuex 实现状态管理。


示例:Vuex 初体验

仓库配置文件(src/store/index.js):

export default new Vuex.Store({
    state: {
        title: 'Hello Vuex',
        count: 0
    },
    // ...
})

根组件(src/App.vue):

<template>
    <div>
        <!--
            通过this.$store.state.xxx访问仓库中的数据
        -->
        <h3>{{this.$store.state.title}}</h3>
        <p>count is {{this.$store.state.count}}</p>
    </div>
</template>

示例效果:


Vuex mutations

在 Vuex 中,不能直接修改 store 中的数据,必须调用相应的事件处理方法处理 state 数据。如果直接修改 store 中的数据,视图页面会发生变化,但 store 中的数据实际上并没有发生变化。


示例:直接修改 store 中的数据

根组件(src/App.vue):

<template>
    <div>
        <h3>{{this.$store.state.title}}</h3>
        <p>count is {{this.$store.state.count}}</p>
        <button @click="changeTitle">title change</button>
    </div>
</template>

<script>
export default {
    methods: {
        changeTitle() {
            this.$store.state.title = 'Hello duozai'
        }
    }
}
</script>

示例效果:


mutations 选项用来定义事件处理方法,用于处理 state 数据。调用 mutations 中的方法,就可以完成数据更新。


示例:使用 mutations 提交数据更新

仓库配置文件(src/store/index.js):

export default new Vuex.Store({
    // ...
    mutations: {
        // state:Vuex.Store.state对象
        // param:要修改的新数据,可以不传
        updateTitle(state, param) {
            state.title = param
        },
        updateCount(state) {
            state.count++
        }
    },
    // ...
})

根组件(src/App.vue):

<template>
    <div>
        <h3>{{this.$store.state.title}}</h3>
        <p>count is {{this.$store.state.count}}</p>
        <button @click="changeTitle">title change</button>
        <button @click="changeCount">count++</button>
    </div>
</template>

<script>
export default {
    methods: {
        changeTitle() {
            // 通过this.$store.commit()调用mutations中的更新数据的方法
            // 参数1:调用的mutations方法名
            // 参数2:新的参数的值,可以不传
            this.$store.commit('updateTitle', 'Hello, duozai')
        },
        changeCount() {
            this.$store.commit('updateCount')
        }
    }
}
</script>

示例效果:


mutations 中定义的方法是同步函数,组件数据发生变化时,触发 mutations 中的事件处理方法来更新数据,这是一种同步更新,在 mutations 中不能有异步操作。


示例:使用 mutations 异步更新数据

仓库配置文件(src/store/index.js):

export default new Vuex.Store({
    // ...
    mutations: {
        updateTitle(state, param) {
            // 模拟异步操作
            setTimeout(function () {
                state.title = param
            }, 3000)
        },
        // ...
    },
    // ...
})

示例效果:


Vuex actions

actions 和 mutations 类似,都用于定义事件处理方法,但 actions 定义的方法,可以执行异步操作,actions 的本质还是调用 mutations。


示例:使用 actions 异步更新数据

仓库配置文件(src/store/index.js):

export default new Vuex.Store({
    // ...
    mutations: {
        updateTitle(state, param) {
            state.title = param
        },
        // ...
    },
    actions: {
        // actions:用于处理异步操作
        // context:Vuex.Store对象
        // param:要修改的新数据,可以不传
        asyncUpdateTitle(context, param) {
            // actions本质上还是调用mutations
            setTimeout(function () {
                context.commit('updateTitle', param)
            }, 3000)
        }
    }
})

根组件(src/App.vue):

export default {
    methods: {
        changeTitle() {
            // 通过this.$store.dispatch()调用actions中的更新数据的方法
            // 参数1:调用的actions方法名
            // 参数2:新的参数的值,可以不传
            this.$store.dispatch('asyncUpdateTitle', 'Hello, duozai')
        },
        // ...
    }
}

示例效果:


Vuex 状态管理模式

在 Vue 中,组件的状态变化是通过单向数据流的设计理念实现的,单向数据流是指只能从一个方向修改状态。

单向数据流的三大关键要素:

  • 状态(State):驱动应用的数据源。
  • 视图(View):以声明方式将状态映射到视图。
  • 操作(Actions):响应在视图上的用户输入导致的状态变化。

Vue 的单向数据流增强了组件之间的独立性,但是存在多个组件共享数据的时候,单向数据流就难以对数据进行维护。

Vuex 就是专门为 Vue 设计的状态(数据)管理库,利用 Vue 的细粒度数据响应机制来进行高效的状态更新。

Vuex 状态管理核心工作流程: