VueJS2-高级教程-十-

95 阅读20分钟

VueJS2 高级教程(十)

原文:Pro Vue.js 2

协议:CC BY-NC-SA 4.0

二十二、URL 路由

在这一章中,我开始描述 URL 路由特性,它建立在我在第二十一章中描述的动态组件的基础上,但是使用当前的 URL 来选择向用户显示的组件。URL 路由是一个复杂的话题,我将在第二十三章和第二十四章中继续描述这个特性的不同方面。表 22-1 将 URL 路由放在上下文中。

表 22-1

将 URL 路由置于上下文中

|

问题

|

回答

| | --- | --- | | 这是什么? | URL 路由根据当前 URL 选择要向用户显示的组件。 | | 为什么有用? | 使用 URL 选择组件允许用户直接导航到应用的特定部分,并允许以比在代码中选择组件更容易维护的方式来组成复杂的应用。 | | 如何使用? | Vue Router 包被添加到一个项目中,一个router-view元素被用来显示应用的路由配置所选择的组件。 | | 有什么陷阱或限制吗? | 可能很难在简明表达路线和创建易于阅读和理解的路线之间找到适当的平衡。 | | 有其他选择吗? | URL 路由是可选的,只有复杂的应用才需要本章描述的功能。 |

表 22-2 总结了本章内容。

表 22-2

章节总结

|

问题

|

解决办法

|

列表

| | --- | --- | --- | | 配置 URL 路由 | 创建一个VueRouter对象,并为其提供一个具有routes属性的配置对象 | 4, 5 | | 显示布线元件 | 使用router-view元素 | six | | 在代码中导航 | 使用$router.push方法 | seven | | 在模板中导航 | 使用路由器链接元素 | eight | | 配置路由模式 | 创建VuewRouter对象时使用mode属性 | nine | | 定义一个总括路线 | 以*为路径定义路线 | Ten | | 定义路线的别名 | 使用 route 属性 | Eleven | | 用代码获取路线的详细信息 | 使用$route对象 | 12–14 | | 控制路由匹配的 URL | 添加动态段、使用正则表达式或使用可选段 | 15–20 | | 为路线指定名称 | 使用name属性 | 21–23 | | 当活动路线改变时接收通知 | 实现一个或多个保护方法 | Twenty-four |

为本章做准备

在本章中,我继续使用第二十一章的 productapp 项目。URL 路由特性需要将名为vue-router的包添加到项目中。运行productapp文件夹中清单 22-1 所示的命令来安装包。

小费

你可以从 https://github.com/Apress/pro-vue-js-2 下载本章以及本书其他章节的示例项目。

npm install vue-router@3.0.1

Listing 22-1Installing the Routing Package

要启动 RESTful web 服务,打开命令提示符并运行清单 22-2 中的命令。

npm run json

Listing 22-2Starting the Web Service

打开第二个命令提示符,导航到productapp目录,运行清单 22-3 中所示的命令来启动 Vue.js 开发工具。

npm run serve

Listing 22-3Starting the Development Tools

一旦初始捆绑过程完成,打开一个新的浏览器窗口并导航到http://localhost:8080,在那里你将看到示例应用,如图 22-1 所示。

img/465686_1_En_22_Fig1_HTML.jpg

图 22-1

运行示例应用

URL 路由入门

在我进入如何使用和配置 URL 路由的细节之前,我将提供一个主要特性的快速介绍,以便您对更详细的主题有一些背景。开始使用 URL 路由的第一步是配置一组路由,它们是应用将支持的 URL 和每个 URL 将显示的组件之间的映射。惯例是将路由配置放在一个名为router的文件夹中,所以我在示例项目中创建了src/router文件夹,并在其中添加了一个名为index.js的文件,代码如清单 22-4 所示。

小费

当您创建一个项目并选择单个特性时,其中一个选项是 Router,它安装vue-router包并设置一个基本的路由配置。

import Vue from "vue";
import VueRouter  from "vue-router";

import ProductDisplay from "../components/ProductDisplay";
import ProductEditor from "../components/ProductEditor";

Vue.use(VueRouter);

export default new VueRouter({
    routes: [
        { path: "/", component: ProductDisplay },
        { path: "/edit", component: ProductEditor}
    ]
})

Listing 22-4The Contents of the index.js File in the src/router Folder

在设置路由包时,获得正确的基本配置是很重要的,所以我将仔细检查清单 22-4 中的每一行代码并解释它的用途,就像我在第二十章中设置 Vuex 数据存储时所做的一样,它遵循类似的模式。第一组语句从其他模块导入路由配置所需的功能。

...
import Vue from "vue";
import VueRouter  from "vue-router";

import ProductDisplay from "../components/ProductDisplay";
import ProductEditor from "../components/ProductEditor";
...

前两个import语句针对 Vue.js 和 Vue 路由器功能。其他的import语句提供了对将要显示给用户的组件的访问,这是一个复杂应用中的一长串语句。

下一条语句启用 Vue 路由器功能:

...
Vue.use(VueRouter);
...

Vue 路由器是作为 Vue.js 插件提供的,它允许 Vue.js 核心功能被扩展,正如我在第二十六章中描述的,插件是用Vue.use方法安装的。

警告

如果你忘记调用Vue.use方法,Vue.js 将不会识别 Vue 路由器包使用的router-viewrouter-link HTML 元素。

下一条语句创建路由配置,并使其成为从routes文件夹中的模块的默认导出:

...
export default new VueRouter({
...

关键字new用于创建一个VueRouter对象,它接受一个配置对象。本例中的配置提供了如何显示两个组件的一组基本指令。

...
routes: [
    { path: "/", component: ProductDisplay },
    { path: "/edit", component: ProductEditor}
]
...

属性用来定义 URL 和组件之间的映射。本例中的映射告诉 Vue Router 为应用的默认路由显示ProductDisplay组件,为/edit URL 显示ProductEditor组件。现在不要担心 URL 路由,因为一旦你看到它们是如何被应用和使用的,它们会更容易理解。

提供对路由配置的访问

下一步是向应用添加路由功能,以便组件可以使用它,如清单 22-5 所示。

import Vue from 'vue'
import App from './App.vue'

import "../node_modules/bootstrap/dist/css/bootstrap.min.css";
import { RestDataSource } from "./restDataSource";
import store from "./store";

import router from "./router";

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  data: {
    eventBus: new Vue()
  },
  store,
  router,

  provide: function () {
      return {
          eventBus: this.eventBus,
          restDataSource: new RestDataSource(this.eventBus)
      }
  }
}).$mount('#app')

Listing 22-5Enabling the Routing Configuration in the main.js File in the src Folder

为了将 URL 路由功能添加到应用中,我使用了一个import语句来指定router模块,并将其命名为router(我不必指定index.js文件,因为这是导入模块时查找的默认名称)。import语句从router文件夹中的index.js文件加载路由配置。我向Vue配置对象添加了一个router属性,这使得应用的组件可以使用 URL 路由功能。

警告

如果您忘记添加清单 22-5 中所示的router属性,URL 路由包将无法正确设置,您将在下面的示例中遇到错误。

使用布线系统显示元件

既然路由系统已经启用,我可以使用它向用户显示组件,如清单 22-6 所示,在这里我使用 URL 路由来替换现有的内容和动态显示组件的代码。

<template>
    <div class="container-fluid">
        <div class="row">
            <div class="col m-2">

                <router-view></router-view>

            </div>
        </div>
    </div>
</template>

<script>
    // import ProductDisplay from "./components/ProductDisplay";

    // import ProductEditor from "./components/ProductEditor";

    // import ErrorDisplay from "./components/ErrorDisplay";

    //import { mapState } from "vuex";

    export default {
        name: 'App',
        // components: { ProductDisplay, ProductEditor, ErrorDisplay },

        created() {
            this.$store.dispatch("getProductsAction");
        },
        // computed: {

        //     ...mapState({

        //         selected: state => state.nav.selected

        //     }),

        //     selectedComponent() {

        //         return this.selected == "table" ? ProductDisplay : ProductEditor;

        //     }

        // }

    }
</script>

Listing 22-6Using URL Routing in the App.vue File in the src Folder

Vue 路由器使用router-view元素来显示内容,取代了之前使用is属性的元素。因为 Vue 路由器包将负责选择由router-view元素显示的组件,所以我能够简化组件的配置对象,删除components属性、计算属性和所有的import语句。这些变化的结果是由元素router-view呈现的内容的责任被委托给 Vue 路由器,产生如图 22-2 所示的结果。(Create New 和 Edit 按钮还没有效果,但是我将很快连接它们。)

小费

添加和更改路由功能时,您可能并不总能得到预期的响应。如果发生这种情况,首先要做的是重新加载浏览器,以获得应用的新副本,这通常会解决问题。

img/465686_1_En_22_Fig2_HTML.jpg

图 22-2

使用 Vue 路由器包

这可能看起来像前面的例子,但有一个重要的区别,这可以通过检查浏览器的 URL 栏看出。当您导航到http://localhost:8080时,浏览器实际上会显示以下 URL:

http://localhost:8080/#/

需要注意的重要部分是 URL 的最后一部分,也就是#/。仔细编辑浏览器 URL 栏中的 URL,以导航到此 URL:

http://localhost:8080/#/edit

这与之前显示的 URL 相同,但是在末尾附加了edit。按回车键,浏览器显示的内容会改变,如图 22-3 所示。

img/465686_1_En_22_Fig3_HTML.jpg

图 22-3

更改 URL 的效果

Vue Router 没有使用数据存储属性来选择向用户显示的组件,而是使用浏览器的 URL,URL 中跟在#字符后面的部分对应于我在清单 22-4 中定义的配置。

...
routes: [
    { path: "/", component: ProductDisplay },

    { path: "/edit", component: ProductEditor}

]
...

path属性指定的值对应于 URL 中跟在#字符后面的部分。Vue Router 监控当前 URL,当它发生变化时,通过根据其path属性找到相应的routes配置项,并显示其 component 属性指定的组件,来选择组件显示在router-view元素中。

导航到不同的 URL

要更改显示给用户的组件,我必须更改浏览器的 URL,此时 Vue Router 会将新的 URL 与其配置进行比较,并显示相应的组件。Vue 路由器提供了导航到新 URL 的特性,在清单 22-7 中,我已经更新了ProductDisplay组件来使用它们。

...
<script>
    import { mapState, mapMutations, mapActions, mapGetters } from "vuex";

    export default {
        computed: {
            ...mapState(["products"]),
            ...mapState({
                useStripedTable: state => state.prefs.stripedTable
            }),
            ...mapGetters({
                tableClass: "prefs/tableClass",
                editClass: "prefs/editClass",
                deleteClass: "prefs/deleteClass"
            })
        },
        methods: {
            editProduct(product) {
                this.selectProduct(product);
                //this.selectComponent("editor");

                this.$router.push("/edit");

            },
            createNew() {
                this.selectProduct();
                //this.selectComponent("editor");

                this.$router.push("/edit");

            },
            ...mapMutations({
                selectProduct: "selectProduct",
                //selectComponent: "nav/selectComponent",

                setEditButtonColor: "prefs/setEditButtonColor",
                setDeleteButtonColor: "prefs/setDeleteButtonColor"
            }),
            ...mapActions({
                deleteProduct: "deleteProductAction"
            })
        },
        created() {
            this.setEditButtonColor(false);
            this.setDeleteButtonColor(false);
        }
    }
</script>
...

Listing 22-7Navigating Programatically in the ProductDisplay.vue File in the src/components Folder

在清单 22-7 中添加router属性的效果是,应用中的所有组件都可以通过$router变量访问 Vue 路由器特性,类似于组件使用$store属性访问 Vuex 数据存储的方式。$router属性返回一个定义表 22-3 中描述的导航方法的对象。

表 22-3

Vue 路由器导航方法

|

名字

|

描述

| | --- | --- | | push(location) | 此方法导航到指定的 URL。此方法接受可选的回调参数,这些参数在导航完成或出现错误时被调用。 | | replace(location) | 该方法执行与push方法相同的任务,但不会在浏览器的历史记录中留下条目,如表后所述。 | | back() | 此方法导航到浏览器历史记录中的上一个 URL。 | | forward() | 此方法导航到浏览器历史记录中的下一个 URL。 |

这两种方法的区别在于,使用push方法执行的导航会在浏览器的历史记录中产生一个条目,其效果是单击浏览器的后退按钮将返回到以前的路线。replace方法改变 URL 而不添加到浏览器的历史中,这意味着向后移动可能会导致浏览器离开 Vue.js 应用。在清单中,我禁用了应用于数据存储突变的selectComponent方法,并将其替换为对 Vue 路由器推送方法的调用,以导航到/edit URL。

...
this.$router.push("/edit");
...

结果是,单击“新建”按钮或“编辑”按钮会告诉 Vue Router 让浏览器导航到#/edit URL。URL 路由是一个两阶段的过程。push方法改变 URL,Vue Router 观察并使用它来改变显示给用户的组件。换句话说,点击一个按钮会改变 URL,进而改变显示给用户的组件,如图 22-4 所示。

img/465686_1_En_22_Fig4_HTML.jpg

图 22-4

在应用中导航

使用 HTML 元素导航

$router方法并不是在应用中导航的唯一方式。Vue Router 还支持一个定制的 HTML 元素,当它被点击时触发导航。$router方法和自定义 HTML 元素可以在一个组件中自由混合,如清单 22-8 所示。

<template>

    <div>
        <div class="form-group">
            <label>ID</label>
            <input class="form-control" v-model="product.id" />
        </div>
        <div class="form-group">
            <label>Name</label>
            <input class="form-control" v-model="product.name" />
        </div>
        <div class="form-group">
            <label>Category</label>
            <input class="form-control" v-model="product.category" />
        </div>
        <div class="form-group">
            <label>Price</label>
            <input class="form-control" v-model.number="product.price" />
        </div>
        <div class="text-center">
            <button class="btn btn-primary" v-on:click="save">
                {{ editing ? "Save" : "Create" }}
            </button>
            <router-link to="/" class="btn btn-secondary">Cancel</router-link>

        </div>
    </div>

</template>

<script>

    let unwatcher;

    export default {
        data: function () {
            return {
                editing: false,
                product: {}
            }
        },
        methods: {
            async save() {
                await this.$store.dispatch("saveProductAction", this.product);
                //this.$store.commit("nav/selectComponent", "table");

                this.$router.push("/");

                this.product = {};
            },
            // cancel() {

            //     this.$store.commit("selectProduct");

            //     //this.$store.commit("nav/selectComponent", "table");

            //     this.$router.push("/");

            // },

            selectProduct(selectedProduct) {
                if (selectedProduct == null) {
                    this.editing = false;
                    this.product = {};
                } else {
                    this.editing = true;
                    this.product = {};
                    Object.assign(this.product, selectedProduct);
                }
            }
        },
        created() {
            unwatcher = this.$store.watch(state =>
                state.selectedProduct, this.selectProduct);
            this.selectProduct(this.$store.state.selectedProduct);
        },
        beforeDestroy() {
           unwatcher();
        }
    }
</script>

Listing 22-8Adding URL Navigation in the ProductEditor.vue File in the src/components Folder

在组件的模板中,我使用了router-link元素来创建一个 HTML 元素,当它被点击时将触发导航。将被导航到的 URL 是使用to属性指定的,如下所示:

...
<router-link to="/" class="btn btn-secondary">Cancel</router-link>
...

在这种情况下,我指定了/,这是配置中定义的第一条路由。当模板被处理并显示给用户时,结果是一个锚(a)元素,如下所示:

...
<a href="#/" class="btn btn-secondary router-link-active">Cancel</a>
...

指定了href属性,使得浏览器导航到的 URL 相对于 URL 的#部分,尽管您不应该在to属性中指定这一点,因为有其他方法可以实现 URL 导航,正如我很快解释的那样。我在本书中使用的引导 CSS 框架支持样式锚元素,因此它们显示为按钮,这允许我将路由链接呈现为它所替换的button元素的无缝替换。由于 URL 导航是由 anchor 元素直接处理的,我已经能够移除cancel方法。

并不是所有的导航都可以仅仅使用 HTML 元素来执行,因为一些额外的任务必须响应用户的动作来执行,正如save方法所展示的。组件模板中的保存/创建按钮不能用router-link元素替换,因为用户输入的数据必须发送到 web 服务。对于这种类型的活动,可以使用$router.push方法,如清单所示。清单 22-8 中的更改允许用户从编辑器导航回表格,或者通过点击保存/创建按钮来保存他们的更改,或者点击取消来放弃它们,如图 22-5 所示。

img/465686_1_En_22_Fig5_HTML.jpg

图 22-5

导航回产品显示

了解和配置 URL 路由匹配

上一节中的例子已经简要介绍了 URL 路由的工作原理,包括定义路由和在应用中导航的不同方式。在接下来的章节中,我会更深入地探讨并解释如何改变 URL 的格式以使它们对用户更友好,以及表达路线以匹配 URL 的不同方式。

了解 URL 匹配和格式

Vue 路由器检查当前 URL,并通过其配置中的路由列表向下查找,直到找到匹配项。要匹配路由,URL 必须包含相同数量的段,并且每个段必须包含路由配置中指定的值。下面是我在清单 22-4 的src/router文件夹的index.js文件中定义的路线:

...
routes: [
    { path: "/", component: ProductDisplay },
    { path: "/edit", component: ProductEditor}
]
...

在考虑应用的路由时,请记住路由是按照定义的顺序匹配的,路由系统只对 URL 的一部分感兴趣。默认情况下,路由系统使用的 URL 部分跟在#字符后面,称为 URL片段或名为锚的*。URL http://localhost:8080/#/edit将匹配路径为/edit的路由,如图 22-6 所示。*

img/465686_1_En_22_Fig6_HTML.jpg

图 22-6

URL 的片段部分

URL 片段最初是指 HTML 文档中的特定位置,以便用户可以在复杂的静态内容中导航。对于 Vue.js 应用,URL 片段用于路由,因为它们可以被更改,而不会导致浏览器向服务器发送 HTTP 请求并丢失应用的状态。

使用 HTML5 历史 API 进行路由

所有浏览器都支持 URL 片段,但结果是应用的 URL 具有奇怪的结构,可能会让用户感到困惑。使用 URL 路由的吸引力之一是用户可以通过改变 URL 直接导航到应用的特定部分,但是如果不理解#字符的重要性,这可能是一个容易出错的过程。例如,如果用户导航到/edit而不是/#/edit,浏览器将假设用户正试图导航到一个新的 URL,并将向服务器发送 HTTP 请求,结果浏览器将导航离开 Vue.js 应用。

一个更好的替代方案是配置 Vue Router,使其使用 HTML 5 历史 API,这允许更健壮和优雅的 URL,但旧浏览器不支持,尽管 Vue Router 会自动退回到使用不支持历史 API 的浏览器中的片段。在清单 22-9 中,我已经更新了路由配置,这样 Vue 路由器将使用历史 API。

import Vue from "vue";
import VueRouter  from "vue-router";

import ProductDisplay from "../components/ProductDisplay";
import ProductEditor from "../components/ProductEditor";

Vue.use(VueRouter);

export default new VueRouter({
    mode: "history",

    routes: [
        { path: "/", component: ProductDisplay },
        { path: "/edit", component: ProductEditor}
    ]
})

Listing 22-9Enabling the History API in the index.js File in the src/router Folder

使用表 22-4 中显示的值,将mode属性添加到 Vue 路由器配置对象中,以指定用于 URL 路由的机制。

表 22-4

模式属性配置值

|

名称

|

描述

| | --- | --- | | hash | 这种模式使用 URL 片段进行路由,这提供了最广泛的浏览器支持,但产生了笨拙的 URL。如果未指定mode属性,这是使用的默认模式。 | | history | 这种模式使用历史 API 进行路由,这提供了最自然的 URL,但不被旧的浏览器支持。 |

在清单中,我为mode属性指定了history值,它告诉 Vue Router 使用历史 API。要查看效果,重新加载浏览器并在应用重新加载后点击其中一个编辑按钮,如图 22-7 所示。

小费

对于本章中的许多示例,您可能需要重新加载浏览器窗口或手动导航到http://localhost:8080来查看更改。

img/465686_1_En_22_Fig7_HTML.jpg

图 22-7

使用历史 API 进行路由

应用导航到了http://localhost:8080/edit,而不是包含片段的 URL,比如http://localhost:8080/#/edit。历史 API 允许使用完整的 URL 进行导航,无需触发浏览器重新加载,其效果是用于路由的 URL 部分发生变化,如图 22-8 所示。

img/465686_1_En_22_Fig8_HTML.jpg

图 22-8

使用历史 API 的效果

注意

对于不支持历史 API(包括旧版本的 Internet Explorer)的浏览器,Vue Router 将自动尝试使用哈希设置,这将使应用返回到使用 URL 片段来定义路由。要禁用这种行为,您可以在路由配置对象中将fallback属性设置为false

提供一条包罗万象的路线

使用历史 API 需要额外的工作来确保用户的流畅体验,用户可以直接导航到对应用有意义的 URL,但是服务器上没有该 URL 的 HTML 文档。在示例应用中,这意味着用户可以在地址栏中键入http://localhost:8080/edit,这将导致浏览器发送一个对名为edit的文档的 HTTP 请求。服务器上没有这样的 HTML 文档,但是 webpack 开发服务器已经配置为使用index.html文件响应任何请求。

请求了什么 URL 并不重要;如果没有可用的内容,服务器将总是返回index.html文件的内容,并且永远不会返回 404-Not Found 错误。当用户请求一个与应用定义的某个路由相对应的 URL 时,这很有用,比如示例应用中的/edit,但是当 URL 与某个路由不相对应时,它会让用户看到一个空窗口。

警告

如果您使用历史 API 进行路由,您必须确保配置您的生产 HTTP 服务器来返回index.html文件的内容。Vue.js 团队在 https://router.vuejs.org/en/essentials/history-mode.html 提供常用生产服务器的说明。

为了解决这个问题,可以定义一个无所不包的路由来匹配任何请求并将其重定向到另一个 URL,如清单 22-10 所示

import Vue from "vue";
import VueRouter  from "vue-router";

import ProductDisplay from "../components/ProductDisplay";
import ProductEditor from "../components/ProductEditor";

Vue.use(VueRouter);

export default new VueRouter({
    mode: "history",
    routes: [
        { path: "/", component: ProductDisplay },
        { path: "/edit", component: ProductEditor},
        { path: "*", redirect: "/" }

    ]
})

Listing 22-10Creating a Catchall Route in the index.js File in the src/router Folder

这个路由的path属性是一个星号(*字符),这允许它匹配任何 URL。这个路由没有component属性,而是有一个redirect属性,它告诉 Vue.js 执行到指定 URL 的重定向。总体效果与 HTTP 服务器的回退功能相结合,这意味着没有内容的请求将使用index.html文件的内容来处理,如果 URL 不对应于某个路由,浏览器将被重定向到/ URL。为了测试这个特性,打开一个新的浏览器窗口并请求http://localhost:8080/does/not/exist URL。如图 22-9 所示,浏览器将显示应用,即使请求的 URL 与应用的任何路由都不对应。

小费

重定向也可以定义为一个函数,它允许将用户请求的 URL 的某些方面合并到重定向的 URL 中。参见第二十三章中的示例。

img/465686_1_En_22_Fig9_HTML.jpg

图 22-9

总括路线的效果

当 Vue.js 应用启动时,Vue Router 检查当前的 URL,并开始通过它的路由寻找匹配。第一个和第二个路由确实与当前 URL 匹配,因为该 URL 既不是/也不是/edit。Vue 路由器到达最终的 URL,它匹配任何内容并导致重定向到/。匹配过程再次开始,但是现在第一条路线匹配了,这导致应用显示ProductDisplay组件。

使用路线别名

使用重定向的一个潜在缺陷是,用户将看到他们在浏览器中键入的 URL 将立即被重定向的 URL 所替换,这可能会令人困惑。另一种方法是为一个路由创建一个别名,这允许它匹配多个 URL 而不需要重定向。在清单 22-11 中,我在路由配置中添加了一个别名,以改变不匹配 URL 的处理方式。

import Vue from "vue";
import VueRouter from "vue-router";

import ProductDisplay from "../components/ProductDisplay";
import ProductEditor from "../components/ProductEditor";

Vue.use(VueRouter);

export default new VueRouter({
    mode: "history",
    routes: [
        { path: "/", component: ProductDisplay, alias: "/list" },

        { path: "/edit", component: ProductEditor } ,
        { path: "*", redirect: "/" }
    ]
})

Listing 22-11Using a Route Alias in the index.js File in the src/router Folder

属性用于为一个路由创建一个别名,这允许它在不使用重定向的情况下匹配多个 URL。我创建的别名将/list URL 定义为根 URL 的别名,可以通过导航到http://localhost:8080/list进行测试。如图 22-10 所示,显示ProductDisplay组件,不修改当前 URL。

img/465686_1_En_22_Fig10_HTML.jpg

图 22-10

使用路由别名

获取组件中的路由数据

除了$router属性之外,Vue Router 包还为组件提供了一个$route属性,该属性描述了当前的路由,可用于调整组件的内容或行为。为了演示,我在示例应用中添加了一条新路线,如清单 22-12 所示。

import Vue from "vue";
import VueRouter from "vue-router";

import ProductDisplay from "../components/ProductDisplay";
import ProductEditor from "../components/ProductEditor";

Vue.use(VueRouter);

export default new VueRouter({
    mode: "history",
    routes: [
        { path: "/", component: ProductDisplay, alias: "/list" },
        { path: "/edit", component: ProductEditor },
        { path: "/create", component: ProductEditor },

        { path: "*", redirect: "/" }
    ]
})

Listing 22-12Adding a Route in the index.js File in the src/router Folder

新的路由匹配/create URL 并指向ProductEditor组件,这意味着有两个不同的 URL——/edit/create——将引导应用显示编辑器特性。在清单 22-13 中,我用一个针对/edit/createURL 的router-link元素替换了ProductDisplay组件模板中的button元素。

...
<template>
    <div>
        <table class="table table-sm table-bordered" v-bind:class="tableClass">
            <tr>
                <th>ID</th><th>Name</th><th>Category</th><th>Price</th><th></th>
            </tr>
            <tbody>
                <tr v-for="p in products" v-bind:key="p.id">
                    <td>{{ p.id }}</td>
                    <td>{{ p.name }}</td>
                    <td>{{ p.category }}</td>
                    <td>{{ p.price }}</td>
                    <td>
                        <router-link to="/edit" v-bind:class="editClass"

                            class="btn btn-sm">

                                Edit

                        </router-link>

                        <button class="btn btn-sm"
                                v-bind:class="deleteClass"
                                v-on:click="deleteProduct(p)">
                            Delete
                        </button>
                    </td>
                </tr>
                <tr v-if="products.length == 0">
                    <td colspan="5" class="text-center">No Data</td>
                </tr>
            </tbody>
        </table>
        <div class="text-center">
            <router-link to="/create" class="btn btn-primary">

                Create New

            </router-link>

        </div>
    </div>
</template>
...

Listing 22-13Targeting Routes in the ProductDisplay.vue File in the src/components Folder

注意,组件不知道导航到/edit/create URL 的结果会是什么。这类似于使用数据存储来协调组件的效果,如第二十一章中所述,其优点是您可以通过编辑路由配置来轻松地重新配置应用。现在有两个不同的 URL 显示了ProductEditor组件,我可以使用$route属性来查看它们中的哪一个被使用了,并更改呈现给用户的内容,如清单 22-14 所示。

<template>

    <div>
        <h3 class="btn-primary text-center text-white p-2">

            {{ editing ? "Edit" : "Create"}}

        </h3>

        <div class="form-group">
            <label>ID</label>
            <input class="form-control" v-model="product.id" />
        </div>
        <div class="form-group">
            <label>Name</label>
            <input class="form-control" v-model="product.name" />
        </div>
        <div class="form-group">
            <label>Category</label>
            <input class="form-control" v-model="product.category" />
        </div>
        <div class="form-group">
            <label>Price</label>
            <input class="form-control" v-model.number="product.price" />
        </div>
        <div class="text-center">
            <button class="btn btn-primary" v-on:click="save">
                {{ editing ? "Save" : "Create" }}
            </button>
            <router-link to="/" class="btn btn-secondary">Cancel</router-link>
        </div>
    </div>

</template>

<script>

    let unwatcher;

    export default {
        data: function () {
            return {
                editing: false,
                product: {}
            }
        },
        methods: {
            async save() {
                await this.$store.dispatch("saveProductAction", this.product);
                this.$router.push("/");
                this.product = {};
            },
            selectProduct(selectedProduct) {
                if (this.$route.path == "/create") {

                    this.editing = false;
                    this.product = {};
                } else {
                    this.product = {};
                    Object.assign(this.product, selectedProduct);
                    this.editing = true;
                }
            }
        },
        created() {
            unwatcher = this.$store.watch(state =>
                state.selectedProduct, this.selectProduct);
            this.selectProduct(this.$store.state.selectedProduct);
        },
        beforeDestroy() {
            unwatcher();
        }
    }
</script>

Listing 22-14Accessing Route Information in the ProductEditor.vue File in the src/components Folder

当调用selectProduct方法时,组件检查$route对象以获得路线的细节。$route属性被赋予一个描述当前路线的对象。表 22-5 描述了$route对象提供的最有用的属性。

表 22-5

有用的 route 属性

|

名字

|

描述

| | --- | --- | | name | 该属性返回路由的名称,如“创建命名路由”一节所述。 | | path | 这个属性返回 URL 路径,比如/edit/4。 | | params | 该属性返回由动态路由匹配的参数的 map 对象,如“动态匹配路由”一节中所述。 | | query | 此属性返回包含查询字符串值的 map 对象。例如,对于 URL /edit/4?validate=true,查询属性将返回一个带有validate属性的对象,该属性的值为true。 |

在清单中,我使用path属性来确定是否使用了/create/edit URL,并相应地配置组件。为了使区别更加明显,我添加了一个h3元素,它使用文本插值绑定来显示标题。为了测试这些更改,您可以单击由ProductDisplay组件显示的新建或编辑按钮,或者直接导航到http://localhost:8080/createhttp://localhost:8080/editURL,这将产生如图 22-11 所示的结果。

img/465686_1_En_22_Fig11_HTML.jpg

图 22-11

响应组件中的不同路线

动态匹配路线

我在上一节中所做的修改的问题是,/edit URL 告诉ProductEditor组件用户想要执行编辑操作,但是没有指定应该编辑哪个对象。

我可以使用一个动态段来解决这个问题,当一个组件需要从 URL 接收数据时,它会被添加到一个路由中。在清单 22-15 中,我扩展了路由配置,使其包含一个动态段。

import Vue from "vue";
import VueRouter from "vue-router";

import ProductDisplay from "../components/ProductDisplay";
import ProductEditor from "../components/ProductEditor";

Vue.use(VueRouter);

export default new VueRouter({
    mode: "history",
    routes: [
        { path: "/", component: ProductDisplay, alias: "/list" },
        { path: "/edit/:id", component: ProductEditor },

        { path: "/create", component: ProductEditor },
        { path: "*", redirect: "/" }
    ]
})

Listing 22-15Using a Dynamic Segment in the index.js File in the src/router Folder

段变量通过在段前加冒号(:字符)来定义,如下所示:

...
{ path: "/edit/:id", component: ProductEditor },
...

该路线的path属性包含两段。第一段匹配以/edit开头的 URL,就像前面的例子一样。第二段是动态的,将匹配任何 URL 段,并将该段的值赋给一个名为id的变量。结果是该路由将匹配任何包含两段的 URL,其中第一段是/edit,例如/edit/10

为了定位新的 URL 及其动态段,我更新了ProductDisplay组件模板中的router-link元素,如清单 22-16 所示。

...
<template>
    <div>
        <table class="table table-sm table-bordered" v-bind:class="tableClass">
            <tr>
                <th>ID</th><th>Name</th><th>Category</th><th>Price</th><th></th>
            </tr>
            <tbody>
                <tr v-for="p in products" v-bind:key="p.id">
                    <td>{{ p.id }}</td>
                    <td>{{ p.name }}</td>
                    <td>{{ p.category }}</td>
                    <td>{{ p.price }}</td>
                    <td>
                        <router-link v-bind:to="'/edit/' +  p.id "

                            v-bind:class="editClass" class="btn btn-sm">

                                Edit
                        </router-link>
                        <button class="btn btn-sm"
                                v-bind:class="deleteClass"
                                v-on:click="deleteProduct(p)">
                            Delete
                        </button>
                    </td>
                </tr>
                <tr v-if="products.length == 0">
                    <td colspan="5" class="text-center">No Data</td>
                </tr>
            </tbody>
        </table>
        <div class="text-center">
            <router-link to="/create" class="btn btn-primary">
                Create New
            </router-link>
        </div>
    </div>
</template>
...

Listing 22-16Targeting a Dynamic Segment in the ProductDisplay.vue File in the src/components Folder

这一变化意味着单击编辑按钮将导航到包含相应对象的id属性的 URL,例如,单击体育场产品的按钮将导航到/edit/5

组件可以通过$route.params属性访问动态段变量。在清单 22-17 中,我已经更新了ProductEditor组件,以便根据id动态段的值从数据存储中检索产品对象。

...
<script>

    let unwatcher;

    export default {
        data: function () {
            return {
                editing: false,
                product: {}
            }
        },
        methods: {
            async save() {
                await this.$store.dispatch("saveProductAction", this.product);
                this.$router.push("/");
                this.product = {};
            },
            selectProduct() {
                if (this.$route.path == "/create") {
                    this.editing = false;
                    this.product = {};
                } else {
                    let productId = this.$route.params.id;

                    let selectedProduct

                        = this.$store.state.products.find(p => p.id == productId);

                    this.product = {};
                    Object.assign(this.product, selectedProduct);
                    this.editing = true;
                }
            }
        },
        created() {
            unwatcher = this.$store.watch(state => state.products,

                this.selectProduct);

            this.selectProduct();

        },
        beforeDestroy() {
            unwatcher();
        }
    }
</script>
...

Listing 22-17Using a Dynamic Segment Value in the ProductEditor.vue File in the src/components Folder

selectProduct方法中,我通过$route对象获取id段的值,并通过数据存储获取用户选择的对象。

这可能很难发现,但是我也在这个清单中更改了数据存储观察器的目标。ProductDisplay组件不再为用户的编辑选择使用数据存储,这可能会使您认为观察器现在是多余的。然而,观察器仍然是必需的,但是它观察对象的产品数组。

...
unwatcher = this.$store.watch(state => state.products, this.selectProduct);
...

用户现在可以直接导航到一个将编辑一个对象的 URL,结果是在用来自 HTTP 请求的数据填充数据存储之前,将向用户显示ProductEditor组件,HTTP 请求由App组件发送到 web 服务。为了确保用户看到他们需要的数据,我使用了一个调用selectProduct方法的数据存储观察器。

小费

Vue 路由器包提供了一种更优雅的等待数据的方式,我在第二十四章对此进行了描述。

这些更改的效果是,单击由ProductDisplay组件呈现的编辑按钮之一,导航到一个 URL,如/edit/4,用户也可以使用浏览器的 URL 栏直接导航到该 URL。

当 URL 被路由系统匹配时,ProductEditor组件读取动态段的值,从数据存储中定位相应的对象,显示给用户编辑,如图 22-12 所示。

img/465686_1_En_22_Fig12_HTML.jpg

图 22-12

使用动态段

使用正则表达式匹配 URL

动态段扩展了路由将匹配的 URL 的范围,但结果可能是路由匹配您稍后要在路由配置中处理的 URL。为了帮助提高路由的精确度,Vue 路由器支持对动态段使用正则表达式,从而对将要匹配的 URL 提供细粒度控制。

在清单 22-18 中,我修改了路由配置,添加了一个带有正则表达式的动态段,并将正则表达式应用于现有的id段。

import Vue from "vue";
import VueRouter from "vue-router";

import ProductDisplay from "../components/ProductDisplay";
import ProductEditor from "../components/ProductEditor";

Vue.use(VueRouter);

export default new VueRouter({
    mode: "history",
    routes: [
        { path: "/", component: ProductDisplay, alias: "/list" },
        { path: "/:op(create|edit)/:id(\\d+)", component: ProductEditor },

        //{ path: "/create", component: ProductEditor },

        { path: "*", redirect: "/" }
    ]
})

Listing 22-18Using Regular Expressions in the index.js File in the src/router Folder

正则表达式应用于动态段名称后的括号中。新的动态段称为op,我应用的正则表达式允许它匹配包含createedit的段,这允许我将两条路线合并为一条,并注释掉专用的/create路线。

...
{ path: "/:op(create|edit)/:id(\\d+)", component: ProductEditor },
...

我应用于id段的正则表达式将匹配仅由一个或多个数字组成的段。这个表达式中的d字符必须用两个反斜杠(\字符)进行转义,以防止它被解释为字面上的d字符,加号(+字符)指定表达式应该只匹配一个或多个数字。

...
{ path: "/:op(create|edit)/:id(\\d+)", component: ProductEditor },
...

结果是,该路由现在将匹配两个段 URL,其中第一个段是/create/edit,第二个段包含一个或多个数字。

定义可选段

我在清单 22-18 中定义的路线并不完全如我所愿,因为它与/create不匹配,而后者是当用户单击 Create New 按钮时ProductDisplay组件导航到的 URL。可以使用问号(?字符)将动态段标记为可选的,问号是用于匹配零个或多个表达式实例的符号。在清单 22-19 中,我使用了一个问号来使id段可选。

import Vue from "vue";
import VueRouter from "vue-router";

import ProductDisplay from "../components/ProductDisplay";
import ProductEditor from "../components/ProductEditor";

Vue.use(VueRouter);

export default new VueRouter({
    mode: "history",
    routes: [
        { path: "/", component: ProductDisplay, alias: "/list" },
        { path: "/:op(create|edit)/:id(\\d+)?", component: ProductEditor },

        { path: "*", redirect: "/" }
    ]
})

Listing 22-19Using an Optional Segment in the index.js File in the src/router Folder

由于id段是可选的,该路由现在将匹配任何单段 URL,如/edit/create,以及任何两段 URL,其中第一段是/edit/create,第二段由一个或多个数字组成。

这个路由配置将匹配一个 URL,例如/create/10,ProductEditor组件中的现有代码将把它视为一个编辑id10的对象的请求。这显示了当您更改应用的路由配置时更新组件的重要性,在清单 22-20 中,我修改了ProductEditor组件,通过检查清单 22-19 中引入的新动态段来避免这个问题。

...
<script>

    let unwatcher;

    export default {
        data: function () {
            return {
                editing: false,
                product: {}
            }
        },
        methods: {
            async save() {
                await this.$store.dispatch("saveProductAction", this.product);
                this.$router.push("/");
                this.product = {};
            },
            selectProduct() {
                if (this.$route.params.op == "create") {

                    this.editing = false;
                    this.product = {};
                } else {
                    let productId = this.$route.params.id;
                    let selectedProduct
                        = this.$store.state.products.find(p => p.id == productId);
                    this.product = {};
                    Object.assign(this.product, selectedProduct);
                    this.editing = true;
                }
            }
        },
        created() {
            unwatcher = this.$store.watch(state => state.products,
                this.selectProduct);
            this.selectProduct();
        },
        beforeDestroy() {
            unwatcher();
        }
    }
</script>
...

Listing 22-20Using a New Segment in the ProductEditor.vue File in the src/components Folder

作为添加到路由配置中的正则表达式的结果,选择了ProductEditor组件的路由不会匹配像/edit/apples这样的 URL。相反,路由系统将通过路由配置继续工作,直到到达catch-all路由,该路由执行到应用根 URL 的重定向,如图 22-13 所示。

img/465686_1_En_22_Fig13_HTML.jpg

图 22-13

在路线中使用正则表达式

创建命名路由

如果您不想将 URL 嵌入到组件的代码和模板中,那么您可以将您的名称分配给您的路由并使用它们。这种方法的优点是很容易改变应用使用的 URL,而不必改变其组件中的所有导航元素和代码,这可以在第二十三章中看到演示。缺点是使用命名路由需要笨拙的语法。在清单 22-21 中,我添加了针对ProductDisplayProductEditor组件的路线名称。

import Vue from "vue";
import VueRouter from "vue-router";

import ProductDisplay from "../components/ProductDisplay";
import ProductEditor from "../components/ProductEditor";

Vue.use(VueRouter);

export default new VueRouter({
    mode: "history",
    routes: [
        { name: "table", path: "/", component: ProductDisplay, alias: "/list" },

        { name: "editor", path: "/:op(create|edit)/:id(\\d+)?",

            component: ProductEditor },

        { path: "*", redirect: "/" }
    ]
})

Listing 22-21Naming a Route in the index.js File in the src/router Folder

name属性用于为一条路线指定一个名称,我已经使用了名称tableeditor。在清单 22-22 中,我使用了一条路线的名称来导航,而不是它的 URL。

<template>

    <div>
        <h3 class="btn-primary text-center text-white p-2">
            {{ editing ? "Edit" : "Create"}}
        </h3>
        <div class="form-group">
            <label>ID</label>
            <input class="form-control" v-model="product.id" />
        </div>
        <div class="form-group">
            <label>Name</label>
            <input class="form-control" v-model="product.name" />
        </div>
        <div class="form-group">
            <label>Category</label>
            <input class="form-control" v-model="product.category" />
        </div>
        <div class="form-group">
            <label>Price</label>
            <input class="form-control" v-model.number="product.price" />
        </div>
        <div class="text-center">
            <button class="btn btn-primary" v-on:click="save">
                {{ editing ? "Save" : "Create" }}
            </button>
            <router-link v-bind:to="{name: 'table'}" class="btn btn-secondary">

                Cancel
            </router-link>
        </div>
    </div>

</template>

<script>

    let unwatcher;

    export default {
        data: function () {
            return {
                editing: false,
                product: {}
            }
        },
        methods: {
            async save() {
                await this.$store.dispatch("saveProductAction", this.product);
                this.$router.push({name: "table"});

                this.product = {};
            },
            selectProduct() {
                if (this.$route.params.op == "create") {
                    this.editing = false;
                    this.product = {};
                } else {
                    let productId = this.$route.params.id;
                    let selectedProduct
                        = this.$store.state.products.find(p => p.id == productId);
                    this.product = {};
                    Object.assign(this.product, selectedProduct);
                    this.editing = true;
                }
            }
        },
        created() {
            unwatcher = this.$store.watch(state => state.products,
                this.selectProduct);
            this.selectProduct();
        },
        beforeDestroy() {
            unwatcher();
        }
    }
</script>

Listing 22-22Navigating by Route Name in the ProductEditor.vue File in the src/components Folder

为了通过名称导航到一条路线,一个具有name属性的对象被传递给$router.push方法或者被分配给一个router-link元素的to属性。name属性的值是所需路线的名称,必须使用v-bind指令来确保 Vue.js 将属性值解释为 JavaScript 表达式。

如果路线有动态段,那么用于导航的对象定义一个用于提供段值的params属性。在清单 22-23 中,我已经更改了导航元素和代码,使用名称并提供参数值。

...
<template>
    <div>
        <table class="table table-sm table-bordered" v-bind:class="tableClass">
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Category</th>
                <th>Price</th>
                <th></th>
            </tr>
            <tbody>
                <tr v-for="p in products" v-bind:key="p.id">
                    <td>{{ p.id }}</td>
                    <td>{{ p.name }}</td>
                    <td>{{ p.category }}</td>
                    <td>{{ p.price }}</td>
                    <td>
                        <router-link v-bind:to="{name: 'editor',

                                        params: { op: 'edit', id: p.id}}"

                            v-bind:class="editClass" class="btn btn-sm">

                                Edit

                        </router-link>

                        <button class="btn btn-sm"
                                v-bind:class="deleteClass"
                                v-on:click="deleteProduct(p)">
                            Delete
                        </button>
                    </td>
                </tr>
                <tr v-if="products.length == 0">
                    <td colspan="5" class="text-center">No Data</td>
                </tr>
            </tbody>
        </table>
        <div class="text-center">
            <router-link v-bind:to="{name: 'editor', params: { op: 'create'}}"

                         class="btn btn-primary">

                Create New
            </router-link>
        </div>
    </div>
</template>
...

Listing 22-23Navigating with Parameters in the ProductDisplay.vue File in the src/components Folder

当使用名称并通过router-link元素提供参数时,需要v-bind指令;否则,Vue.js 不会将to属性的值解释为 JavaScript 对象,而是将该值视为 URL。

处理导航更改

当一个路由变更需要新的内容时,现有的组件被销毁,新组件的一个实例被创建,遵循我在第十七章和第二十一章中描述的生命周期。当路由更改显示相同的组件时,Vue 路由器包只是重用现有的组件,并通知它已经发生了更改。为了演示这个问题,我向ProductEditor组件添加了新的导航特性,允许用户在可供编辑的对象间移动,如清单 22-24 所示。

<template>

    <div>
        <h3 class="btn-primary text-center text-white p-2">
            {{ editing ? "Edit" : "Create"}}
        </h3>
        <div class="form-group">
            <label>ID</label>
            <input class="form-control" v-model="product.id" />
        </div>
        <div class="form-group">
            <label>Name</label>
            <input class="form-control" v-model="product.name" />
        </div>
        <div class="form-group">
            <label>Category</label>
            <input class="form-control" v-model="product.category" />
        </div>
        <div class="form-group">
            <label>Price</label>
            <input class="form-control" v-model.number="product.price" />
        </div>
        <div class="text-center">

            <button class="btn btn-primary" v-on:click="save">
                {{ editing ? "Save" : "Create" }}
            </button>
            <router-link to="{name: 'table'}" class="btn btn-secondary">
                Cancel
            </router-link>

            <router-link v-if="editing" v-bind:to="nextUrl" class="btn btn-info">

                Next

            </router-link>

        </div>
    </div>

</template>

<script>

    let unwatcher;

    export default {
        data: function () {
            return {
                editing: false,
                product: {}
            }
        },
        computed: {

            nextUrl() {

                if (this.product.id != null && this.$store.state.products != null) {

                    let index = this.$store.state.products

                        .findIndex(p => p.id == this.product.id);

                    let target = index < this.$store.state.products.length - 1

                        ? index + 1 : 0

                    return `/edit/${this.$store.state.products[target].id}`;

                }

                return "/edit";

            }

        },

        methods: {
            async save() {
                await this.$store.dispatch("saveProductAction", this.product);
                this.$router.push({name: "table"});
                this.product = {};
            },
            selectProduct(route) {

                if (route.params.op == "create") {

                    this.editing = false;
                    this.product = {};
                } else {
                    let productId = route.params.id;

                    let selectedProduct
                        = this.$store.state.products.find(p => p.id == productId);
                    this.product = {};
                    Object.assign(this.product, selectedProduct);
                    this.editing = true;
                }
            }
        },
        created() {
            unwatcher = this.$store.watch(state => state.products,

                () => this.selectProduct(this.$route));

            this.selectProduct(this.$route);

        },
        beforeDestroy() {
            unwatcher();
        },
        beforeRouteUpdate(to, from, next) {

            this.selectProduct(to);

            next();

        }

    }
</script>

Listing 22-24Adding Navigation Features in the ProductEditor.vue File in the src/components Folder

我添加了一个router-link元素,该元素在编辑产品时显示,并导航到数据存储中的下一个产品。显示给用户的组件不会改变,这意味着 Vue Router 不会销毁ProductEditor组件的现有实例并创建一个新实例。

组件可以实现从路由系统接收通知的方法。我在第二十四章中描述了所有的方法,但是对于这一章来说,最重要的是beforeRouteUpdate方法,因为在不破坏组件的情况下,当路线将要改变时会调用这个方法。beforeRouteUpdate方法定义了三个参数,在表 22-6 中有描述。

表 22-6

beforeRouteUpdate 参数

|

名字

|

描述

| | --- | --- | | to | 此参数被赋予一个对象,该对象描述应用将要导航到的路线。 | | from | 此参数被赋予一个描述当前路线的对象,应用将导航离开该路线。 | | next | 此参数被赋予一个函数,必须调用该函数才能允许其他组件处理通知。它还可以用来控制导航过程,我在第二十四章对此进行了描述。 |

通过tofrom参数接收的对象定义了与$route对象相同的属性集,如表 22-5 所述。在清单中,beforeRouteUpdate方法的实现将to对象传递给selectProduct方法,以便组件可以更新其状态。请记住,活动路由尚未更改,因此我不能使用$route对象来响应更新。一旦我处理了更改,我就调用next函数,它允许应用中的其他组件接收通知(这似乎是一个奇怪的要求,但是next函数也可以用来阻止或改变导航,我在第二十四章中对此进行了描述)。

要测试路由通知,请导航至http://localhost:8080,点击其中一个编辑按钮,然后点击下一步按钮浏览产品对象,如图 22-14 所示。

img/465686_1_En_22_Fig14_HTML.jpg

图 22-14

响应路由通知

摘要

在本章中,我向您展示了如何使用 URL 路由动态选择组件。我向您展示了定义路由的不同方法,如何使 HTML5 历史 API 能够在没有 URL 片段的情况下路由,如何提供一个无所不包的路由,以及如何命名路由并为它们创建别名。我还演示了使用动态段从 URL 获取数据,并向您展示了当路由更改将显示相同的组件时如何接收通知。在下一章,我将更详细地描述用于管理路由的 HTML 元素。

二十三、URL 路由元素功能

在这一章中,我描述了由router-linkrouter-view元素提供的一些特性。我将向您展示如何更改处理router-link元素的方式,如何使用不同的导航事件,以及如何通过更改应用于导航元素的样式来响应路由激活。我还将向您展示如何在一个应用中使用多个router-view元素,以及当两个或多个router-view元素出现在同一个模板中时,如何管理它们。表 23-1 将本章放入上下文中。

表 23-1

将路由元素功能放在上下文中

|

问题

|

回答

| | --- | --- | | 它们是什么? | router-link元素提供了一些特性,这些特性控制生成的 HTML、触发导航的事件以及元素将被添加到的类,以指示活动路线。router-view元素提供了允许多个元素存在于单个组件模板中的特性。 | | 它们为什么有用? | 当您需要通过非标准元素呈现导航,或者想要使用 CSS 框架中的样式提供反馈时,router-link特性非常有用。router-view特性在复杂的应用中很有用,因为它们允许创建更高级的内容组合。 | | 它们是如何使用的? | 使用router-linkrouter-view元素上的属性来应用这些特性,并在应用的路由中提供相应的支持。 | | 有什么陷阱或限制吗? | 必须注意确保定义了合适的总括路线和重定向,以确保应用不会向用户显示空窗口或通过导航元素提供令人困惑的反馈。 | | 还有其他选择吗? | 这些特性是可选的,你不必使用它们。 |

表 23-2 总结了本章内容。

表 23-2

章节总结

|

问题

|

解决办法

|

列表

| | --- | --- | --- | | 配置用于导航的元素 | 使用由router-link元素提供的属性 | 4–7 | | 当元素与活动 URL 匹配时设置元素样式 | 定义选择router-link-activerouter-link-exact-active类的样式 | eight | | 确保仅当元素与活动路线完全匹配时,才设计元素的样式 | 应用exact属性 | nine | | 更改用于指示活动航路的类别 | 使用exact-active-classactive-class属性 | Ten | | 创建嵌套路线 | 在应用中应用多个router-view元素,并用使用children属性定义的路由来定位它们 | 11–17 |

为本章做准备

在本章中,我继续使用第二十三章中的 productapp 项目。为了准备本章,我已经注释掉了ProductDisplay组件中的create方法,如清单 23-1 所示。该方法中的语句用于演示第二十章中数据存储的使用,本章并不需要这些语句,因为每次创建ProductDisplay组件的新实例时,这些语句都会导致数据存储值重置。

...
<script>
    import { mapState, mapMutations, mapActions, mapGetters } from "vuex";

    export default {
        computed: {
            ...mapState(["products"]),
            ...mapState({
                useStripedTable: state => state.prefs.stripedTable
            }),
            ...mapGetters({
                tableClass: "prefs/tableClass",
                editClass: "prefs/editClass",
                deleteClass: "prefs/deleteClass"
            })
        },
        methods: {
            editProduct(product) {
                this.selectProduct(product);
                this.$router.push("/edit");
            },
            createNew() {
                this.selectProduct();
                this.$router.push("/edit");
            },
            ...mapMutations({
                selectProduct: "selectProduct",
                setEditButtonColor: "prefs/setEditButtonColor",
                setDeleteButtonColor: "prefs/setDeleteButtonColor"
            }),
            ...mapActions({
                deleteProduct: "deleteProductAction"
            })
        },
        //created() {

        //    this.setEditButtonColor(false);

        //    this.setDeleteButtonColor(false);

        //}

    }
</script>
...

Listing 23-1Disabling a Method in the ProductDisplay.vue File in the src/components Folder

要启动 RESTful web 服务,打开命令提示符并运行清单 23-2 中的命令。

npm run json

Listing 23-2Starting the Web Service

打开第二个命令提示符,导航到productapp目录,运行清单 23-3 中所示的命令来启动 Vue.js 开发工具。

npm run serve

Listing 23-3Starting the Development Tools

一旦初始捆绑过程完成,打开一个新的浏览器窗口并导航到http://localhost:8080,在那里你将看到示例应用,如图 23-1 所示。

小费

你可以从 https://github.com/Apress/pro-vue-js-2 下载本章以及本书其他章节的示例项目。

img/465686_1_En_23_Fig1_HTML.jpg

图 23-1

运行示例应用

使用路由器链接元素

router-link元素比它第一次出现时更加灵活,并且支持一些有用的选项来定制它生成的 HTML 元素,并向用户提供有用的反馈。为了准备接下来的部分,我在App组件的模板中添加了router-link元素,如清单 23-4 所示。

<template>
    <div class="container-fluid">
        <div class="row">

            <div class="col text-center m-2">

                <router-link to="/list" class="m-1">List</router-link>

                <router-link to="/create" class="m-1">Create</router-link>

            </div>

        </div>

        <div class="row">
            <div class="col m-2">
                <router-view></router-view>
            </div>
        </div>
    </div>
</template>

<script>

    export default {
        name: 'App',
        created() {
            this.$store.dispatch("getProductsAction");
        }
    }
</script>

Listing 23-4Adding Navigation Elements in the App.vue File in the src Folder

这些router-link元素作为组件模板的一部分被处理,并被转换成锚元素,如图 23-2 所示。

img/465686_1_En_23_Fig2_HTML.jpg

图 23-2

使用路由器链接元素导航

通过应用表 23-3 中描述的属性来配置router-link元素,其中最有用的我将在接下来的章节中演示。

表 23-3

路由器链路属性

|

名字

|

描述

| | --- | --- | | tag | 该属性指定了转换router-link元素时将生成的 HTML 元素的标记类型,如“选择元素类型”一节所述。 | | event | 该属性指定将触发导航的事件,如“选择导航事件”一节中所述。 | | exact | 此属性指定在标识对应于活动路由的元素时是否使用部分 URL 匹配,如“设计路由器链接元素”一节中所述。 | | active-class | 该属性指定当活动 URL 以元素的导航目标开始时,元素将被添加到的类,如“设计路由器链接元素”一节中所述。 | | exact-active-class | 该属性指定当活动 URL 匹配元素的导航目标时,元素将被添加到的类,如“设计路由器链接元素”一节中所述。 | | to | 该属性指定了导航位置,并将向浏览器的历史记录中添加一个条目,相当于第二十二章中描述的push导航方法。 | | replace | 该属性指定导航位置,但不会向浏览器的历史记录中添加条目,相当于第二十二章中描述的replace导航方法。 | | append | 此属性指定一个相对 URL,当您使用用户提供的数据进行导航,并希望确保导航包含在应用的特定部分时,此属性会很有用。 |

选择元素类型

默认情况下,router-link元素被转换成锚点,也就是带有a标签的元素。如果您使用浏览器的 F12 工具来检查文档对象模型,您可以看到我在清单 23-4 中添加的router-link元素是如何被转换的。

...
<div class="col text-center m-2">
    <a href="/list" class="m-1">List</a>

    <a href="/create" class="m-1">Create</a>

</div>
...

小费

如果你点击了其中一个a元素,你会看到它已经被添加到了router-link-activerouter-link-exact-active类中。我将在“设计路由器链接元素”一节中解释这些元素的含义。

tag属性可用于选择不同的元素类型,当router-link元素被转换时,该元素类型将代替锚点使用。当您想要以不能使用a元素的方式呈现一系列导航元素时,或者当您想要在选择器不能匹配锚元素的情况下应用 CSS 样式时,这是非常有用的。在清单 23-5 中,我使用了tag属性来填充一个包含导航元素的列表。

<template>
    <div class="container-fluid">
        <div class="row">
            <div class="col text-center m-2">
                <ol>

                    <router-link tag="li" to="/list">List</router-link>

                    <router-link tag="li" to="/create">Create</router-link>

                </ol>

            </div>
        </div>
        <div class="row">
            <div class="col m-2">
                <router-view></router-view>
            </div>
        </div>
    </div>
</template>

<script>

    export default {
        name: 'App',
        created() {
            this.$store.dispatch("getProductsAction");
        }
    }
</script>

Listing 23-5Specifying Tag Type in the App.vue File in the src Folder

tag属性指定当组件的模板被处理时,router-link元素应该被替换为li元素。如果保存更改并使用浏览器的 F12 开发工具来检查文档对象模型,您将看到以下元素:

...
<ol>
    <li class="">List</li>

    <li class="">Create</li>

</ol>
...

当你点击其中一个li元素时,导航发生,如图 23-3 所示。

img/465686_1_En_23_Fig3_HTML.jpg

图 23-3

更改导航元素类型

选择导航事件

默认的导航事件是click,这意味着当用户点击由router-link元素创建的元素时,导航被执行。event属性用于指定一个可选事件,它允许以不同的方式进行导航。在清单 23-6 中,我使用了event属性,这样当用户将鼠标指针移动到导航元素上时就会执行导航。

警告

用户希望当他们点击一个元素时会出现导航,因为这是大多数 web 应用的工作方式。小心使用event属性,因为您很容易混淆您的用户并产生意想不到的结果。

<template>
    <div class="container-fluid">
        <div class="row">
            <div class="col text-center m-2">
                <ol>
                    <router-link tag="li" event="mouseenter" to="/list">

                        List

                    </router-link>

                    <router-link tag="li" event="mouseenter" to="/create">

                        Create

                    </router-link>

                </ol>
            </div>
        </div>
        <div class="row">
            <div class="col m-2">
                <router-view></router-view>
            </div>
        </div>
    </div>
</template>

<script>

    export default {
        name: 'App',
        created() {
            this.$store.dispatch("getProductsAction");
        }
    }
</script>

Listing 23-6Specifying the Navigation Event in the App.vue File in the src Folder

当鼠标指针进入由 HTML 元素占据的浏览器窗口区域时,触发mouseenter事件,这意味着无需用户点击鼠标按钮就可以进行导航。

设计路由器链接元素的样式

当你对router-link元素应用样式时,重要的是要记住你是在对router-link被转换成的元素进行样式化,而不是对router-link元素本身。在清单 23-7 中,我为App组件添加了一个style属性,用于定义导航元素的样式。

<template>
    <div class="container-fluid">
        <div class="row">
            <div class="col text-center m-2">
                <ol>
                    <router-link tag="li" event="mouseenter" to="/list">
                        List
                    </router-link>
                    <router-link tag="li" event="mouseenter" to="/create">
                        Create
                     </router-link>
                </ol>
            </div>
        </div>
        <div class="row">
            <div class="col m-2">
                <router-view></router-view>
            </div>
        </div>
    </div>
</template>

<script>

    export default {
        name: 'App',
        created() {
            this.$store.dispatch("getProductsAction");
        }
    }
</script>

<style scoped>

    router-link { text-align: right; color: yellow; background-color: red; }

    li { text-align: left; color:blue; background-color: lightblue; }

</style>

Listing 23-7Styling Navigation Elements in the App.vue File in the src Folder

重要的是要记住,是浏览器在将router-link元素转换成由tag属性指定的元素类型之后评估样式选择器。出于这个原因,清单 23-7 中定义的第一个样式将不匹配任何元素,因为在组件的模板被处理后没有router-link元素可供选择。这是混淆的一个常见原因,尤其是在使用自动管理 CSS 样式的工具时。第二种样式的选择器匹配转换后的元素,并将应用于组件的内容,如图 23-4 所示。

小费

您可能需要重新加载浏览器才能看到如图 23-4 所示的新样式。

img/465686_1_En_23_Fig4_HTML.jpg

图 23-4

样式导航元素

响应活动路由

当当前 URL 匹配导航元素的目标时,Vue Router 包将该元素添加到router-link-activerouter-link-exact-active类中,这两个类可用于应用向用户提供反馈的样式。在清单 23-8 中,我定义了在选择器中使用这些类的样式。我还删除了事件属性,这样当用户点击时就会出现导航,我还添加了新的router-link元素,导航到/edit/edit/1URL。

<template>
    <div class="container-fluid">
        <div class="row">
            <div class="col text-center m-2">
                <ol>
                    <router-link tag="li" to="/list">List</router-link>

                    <router-link tag="li" to="/create">Create</router-link>

                    <router-link tag="li" to="/edit">Edit</router-link>

                    <router-link tag="li" to="/edit/1">Edit Kayak</router-link>

                </ol>
            </div>
        </div>
        <div class="row">
            <div class="col m-2">
                <router-view></router-view>
            </div>
        </div>
    </div>
</template>

<script>

    export default {
        name: 'App',
        created() {
            this.$store.dispatch("getProductsAction");
        }
    }
</script>

<style scoped>
    li { text-align: left; color:blue; background-color: lightblue; }
    .router-link-active { font-size: xx-large; }

    .router-link-exact-active { font-weight: bolder; }

</style>

Listing 23-8Styling the Active Navigation Element in the App.vue File in the src Folder

当路线发生变化时,导航元素会自动添加到类中或从类中移除。如果由to属性指定的目标与当前 URL 完全匹配,那么元素将被添加到router-link-exact-active类中,该类应用使元素文本加粗的样式。如果当前 URL 以由to属性指定的目标开始,则元素被添加到router-link-active类中。你可以通过点击编辑 Kayak 链接看到不同之处,这将导航到/edit/1网址。目标为/edit的编辑元素被添加到router-link-active类中,因为当前 URL 以其目标开始:/edit/1/edit开始。Edit Kayak 链接被添加到这两个类中,因为当前 URL 以其目标开始,并且与其目标完全匹配。结果是编辑元素以更大的文本显示,而编辑 Kayak 链接以同样加粗的更大文本显示,如图 23-5 所示。

img/465686_1_En_23_Fig5_HTML.jpg

图 23-5

响应活动的路由类别

使用router-link-active类进行部分 URL 匹配并不总是有用的,可以通过向router-link元素添加exact属性来禁用,如清单 23-9 所示。

...
<template>
    <div class="container-fluid">
        <div class="row">
            <div class="col text-center m-2">
                <ol>
                    <router-link tag="li" to="/list">List</router-link>
                    <router-link tag="li" to="/create">Create</router-link>
                    <router-link tag="li" to="/edit" exact>Edit</router-link>

                    <router-link tag="li" to="/edit/1">Edit Kayak</router-link>
                </ol>
            </div>
        </div>
        <div class="row">
            <div class="col m-2">
                <router-view></router-view>
            </div>
        </div>
    </div>
</template>
...

Listing 23-9Disabling Partial URL Matching in the App.vue File in the src Folder

无论分配给它的值是什么,exact属性都将生效,并且禁用部分匹配特性的是属性的存在,而不是它的值。当用户导航到/edit/1 URL 时,清单中应用的属性阻止编辑元素被添加到router-link-active类,如图 23-6 所示。

img/465686_1_En_23_Fig6_HTML.jpg

图 23-6

禁用部分 URL 映射

更改活动路线类别

当你使用一个 CSS 框架时,比如我在本书中使用的 Bootstrap 框架,你会发现经常有一些类被用来指示一个元素何时是活动的,这些类并不对应于 Vue 路由器使用的类的名称。active-classexact-active-class属性可以用来指定类的名称,当元素的目标与当前 URL 匹配时,应该将元素添加到这些类中。在清单 23-10 中,我用一组更传统的按钮元素替换了导航元素列表,并使用了active-class-exact属性来指定用于指示活动按钮的引导类的名称。我还删除了style元素,因为我不再需要定制的 CSS 样式。

小费

您可以通过使用路由配置对象中的linkActiveClasslinkExactActiveClass属性来更改用于全局指示路由的类,这意味着您不必在每个元素上指定这些类。

<template>
    <div class="container-fluid">
        <div class="row">
            <div class="col text-center m-2">
                <div class="btn-group">

                    <router-link tag="button" to="/list"

                                 exact-active-class="btn-info"

                                 class="btn btn-primary">

                        List

                    </router-link>

                    <router-link tag="button" to="/create"

                                 exact-active-class="btn-info"

                                 class="btn btn-primary">

                        Create

                    </router-link>

                    <router-link tag="button" to="/edit"

                                 exact-active-class="btn-info"

                                 class="btn btn-primary">

                        Edit

                    </router-link>

                    <router-link tag="button" to="/edit/1"

                                 exact-active-class="btn-info"

                                 class="btn btn-primary">

                        Edit Kayak

                    </router-link>

                </div>

            </div>
        </div>
        <div class="row">
            <div class="col m-2">
                <router-view></router-view>
            </div>
        </div>
    </div>
</template>

<script>

    export default {
        name: 'App',
        created() {
            this.$store.dispatch("getProductsAction");
        }
    }
</script>

Listing 23-10Specifying Active Class Names in the App.vue File in the src Folder

新的router-link元素都被赋给了btnbtn-primary类,这两个类是样式按钮的引导类,当它们表示活动路线时将被赋给btn-info类,效果如图 23-7 所示。

img/465686_1_En_23_Fig7_HTML.jpg

图 23-7

更改活动路线类别

创建嵌套路线

到目前为止,路由示例都假设应用只有一组要显示的组件,并且当选择其中一个组件时,它将总是显示相同的内容。在复杂的应用中,一个顶级组件可能需要显示不同的子组件,为了支持这一需求,Vue Router 包支持嵌套路由,也称为子路由

规划应用布局

使用嵌套路由时,理解您的目标很重要。对于示例应用,我将为用户提供顶级导航元素,允许用户在产品相关功能和设置应用首选项的组件之间进行选择。图 23-8 显示了我打算创建的应用结构。

img/465686_1_En_23_Fig8_HTML.jpg

图 23-8

应用的结构

决定应用将支持的 URL 集使得创建路由更加简单。我将在示例应用中使用的 URL 在表 23-4 中描述,并遵循我在早期示例中使用的相同基本方法。

表 23-4

示例应用的 URL

|

统一资源定位器

|

描述

| | --- | --- | | /products/table | 该 URL 将显示产品列表。 | | /products/create | 该 URL 将显示用于创建新产品的编辑器。 | | /products/edit/10 | 此 URL 将显示用于修改指定产品的编辑器。 | | /preferences | 此 URL 将显示首选项设置。 |

向项目中添加组件

为了创建我需要的结构,我需要向项目添加一个组件。首先,我在src/components文件夹中添加了一个名为Preferences.vue的文件,其内容如清单 23-11 所示。

<template>
    <div>
        <h4 class="bg-info text-white text-center p-2">Preferences</h4>
        <div class="form-check">
            <input class="form-check-input" type="checkbox"
                   v-bind:checked="primaryEdit" v-on:input="setPrimaryEdit">
            <label class="form-check-label">Primary Color for Edit Buttons</label>
        </div>
        <div class="form-check">
            <input class="form-check-input" type="checkbox"
                   v-bind:checked="dangerDelete" v-on:input="setDangerDelete">
            <label class="form-check-label">Danger Color for Delete Buttons</label>
        </div>
    </div>
</template>

<script>

    import { mapState } from "vuex";

    export default {
        computed: {
            ...mapState({
                primaryEdit: state => state.prefs.primaryEditButton,
                dangerDelete: state => state.prefs.dangerDeleteButton
            })
        },
        methods: {
            setPrimaryEdit() {
                this.$store.commit("prefs/setEditButtonColor", !this.primaryEdit);
            },
            setDangerDelete() {
                this.$store.commit("prefs/setDeleteButtonColor", !this.dangerDelete);
            }
        }
    }
</script>

Listing 23-11The Contents of the Preferences.vue File in the src/components Folder

这是将向用户显示首选项的组件。它使用复选框显示数据存储中两个状态属性的值,并在用户切换控件时更新这些值。这些是相同的数据存储状态属性,用于设置由ProductDisplay组件显示的编辑和删除按钮的颜色。

接下来,我在src/components文件夹中添加了一个名为Products.vue的文件,其内容如清单 23-12 所示。

<template>
    <router-view></router-view>
</template>

Listing 23-12The Contents of the Products.vue file in the src/component Folder

这个组件只包含一个template元素,而这个元素又只包含一个router-view元素。这个组件将允许我显示产品列表或编辑器。

定义路线

组件就绪后,我可以定义应用的路由配置来实现表 23-4 中描述的 URL 集,如清单 23-13 所示。

import Vue from "vue";
import VueRouter from "vue-router";

import ProductDisplay from "../components/ProductDisplay";
import ProductEditor from "../components/ProductEditor";

import Preferences from "../components/Preferences";

import Products from "../components/Products";

Vue.use(VueRouter);

export default new VueRouter({
    mode: "history",
    routes: [
        { path: "/preferences", component: Preferences},

        { path: "/products", component: Products,

            children: [

                { name: "table", path: "list", component: ProductDisplay},

                { name: "editor", path: ":op(create|edit)/:id(\\d+)?",

                      component: ProductEditor},

                { path: "", redirect: "list" }

            ]

        },

        { path: "/edit/:id", redirect: to => `/products/edit/${to.params.id}`},

        { path: "*", redirect: "/products/list" }

    ]
})

Listing 23-13Defining Routes in the index.js File in the src/router Folder

这些路由包含大量信息,因此我将逐一展开,并解释它们如何构成我在本节开始时描述的结构。第一条路线遵循您在前面的示例中看到的格式,如下所示:

...
{ path: "/preferences", component: Preferences},
...

这个路由告诉 Vue Router 在 URL 为/preferences时显示Preferences组件。该路线中没有使用动态段、名称、重定向或其他特殊功能,所选组件将显示在App组件模板中定义的router-view元素中。

下一条路线更复杂,最好是逐步接近。第一部分很简单。

...
{ path: "/products", component: Products,
...

pathcomponent属性告诉 Vue Router 当 URL 为/products时应该显示Products组件。与前面的路线一样,Products组件将显示在App组件模板的router-view元素中。但是Product组件的模板还包含一个router-view元素,必须为其选择一个组件,这就是该路由定义的children属性的用途:

...
{ path: "/products", component: Products,
    children: [

        { name: "table", path: "list", component: ProductDisplay},

        { name: "editor", path: ":op(create|edit)/:id(\\d+)?",

            component: ProductEditor},

        { path: "", redirect: "list" }

    ]

},
...

children属性用于定义一组路由,这些路由将应用于Products组件模板中的router-view元素。每个子路由的path属性的值与其父路由的path相结合,以匹配一个 URL 并选择一个组件,以便为/products/list URL 选择ProductDisplay组件。子路由可以包括动态段和正则表达式,这可以在为/create/edit/idURL 选择ProductEditor组件的路由中看到。

children部分中的最后一个路由是一个将执行重定向的总括路由,这样任何以/products开头但与前两个路由不匹配的 URL 都将被重定向到/products/list。注意,这个路由的路径是一个空字符串,而不是一个星号,因为我想匹配没有这个段的值的 URL。

处理旧的 URL

当现有应用支持的 URL 集发生变化时,一定要确保更新组件中使用的 URL,或者在新旧 URL 之间创建重定向或别名。编辑功能以前通过/edit/:id路径访问,但现在通过/products/edit/:id访问。为了确保旧的 URL 仍然有效,我在清单 23-13 中定义了重定向路由。

...
{ path: "/edit/:id", redirect: to => `/products/edit/${to.params.id}`},
...

在第二十二章中,我使用一个固定的 URL 创建了一个重定向。这在这种情况下是行不通的,因为我需要将动态id段的值传递给新路线。如清单所示,重定向也可以表示为一个函数,它接收匹配的路由并返回重定向 URL。在这个例子中,重定向函数接收路由并组成重定向 URL,以便它包含id值。

创建导航元素

在清单 23-14 中,我用针对表 23-4 中描述的 URL 的按钮替换了App组件模板中的导航按钮。

<template>
    <div class="container-fluid">
        <div class="row">
            <div class="col text-center m-2">
                <div class="btn-group">
                    <router-link tag="button" to="/products" active-class="btn-info"

                            class="btn btn-primary">

                        Products

                    </router-link>

                    <router-link tag="button" to="/preferences"

                             active-class="btn-info" class="btn btn-primary">

                        Preferences

                    </router-link>

                </div>
            </div>
        </div>
        <div class="row">
            <div class="col m-2">
                <router-view></router-view>
            </div>
        </div>
    </div>
</template>

<script>

    export default {
        name: 'App',
        created() {
            this.$store.dispatch("getProductsAction");
        }
    }
</script>

Listing 23-14Navigating to the New URLs in the App.vue File in the src Folder

这些router-link元素将被转换成button元素,这些元素导航到/products/preferencesURL,并使用 Bootstrap btnbtn-primary类进行样式化,这些类在 Bootstrap 配色方案的原色中应用基本的按钮样式。

为了表明什么时候button代表活动路线,我将元素添加到了btn-info类,该类使用active-class属性为按钮应用了不同的引导颜色。

测试嵌套路由

支持嵌套的router-view元素所需的所有更改都已就绪。您可以通过导航到http://localhost:8080并使用ProductsPreferences按钮来更改App组件的模板中的router-view元素的内容,这将产生如图 23-9 所示的结果。

img/465686_1_En_23_Fig9_HTML.jpg

图 23-9

为顶级路由器视图元素选择组件

要查看嵌套的router-view元素,单击 Products 按钮,然后单击表格中显示的编辑按钮之一。Products组件中的router-view元素所显示的组件将变为显示编辑器,点击保存或取消按钮可以返回到表格视图,如图 23-10 所示。

img/465686_1_En_23_Fig10_HTML.jpg

图 23-10

为嵌套路由器视图元素选择组件

请注意,使用命名路由的router-link元素和代码会自动将使用新路由的 URL 作为目标。当我在清单 23-13 中定义路由时,我将在第二十二章中定义的名称应用于新配置中的相应路由。

...
{ path: "/products", component: Products,
    children: [
        { name: "table", path: "list", component: ProductDisplay},
        { name: "editor", path: ":op(create|edit)/:id(\\d+)?",
            component: ProductEditor},
        { path: "", redirect: "list" }
    ]
},
...

当使用命名路由的router-link元素被处理时,结果是一个指向指定路由的 URL,这意味着名称保持有效,即使它们所涉及的路由发生了变化。您可以在表格视图中显示的编辑按钮中看到这一点,这些按钮是用这个router-link元素创建的:

...
<router-link v-bind:to="{name: 'editor', params: { op: 'edit', id: p.id}}"

        v-bind:class="editClass" class="btn btn-sm">
    Edit
</router-link>
...

使用名称设置to属性,该名称指定编辑器路径。处理此元素时,结果是一个锚元素,其目标对应于命名的路由,如下所示:

...
<a href="/products/edit/1" class="btn btn-sm btn-secondary">
    Edit
</a>
...

使用命名路由可能需要笨拙的代码和 HTML,但结果可以是更灵活和更健壮的应用,该应用适应其路由配置的变化,而不需要其组件的相应变化。

使用命名路由器视图元素

一些组件需要在同一个模板中有多个router-view元素,这样就可以动态地选择两个或更多的子组件。当router-view元素在同一个模板中时,name属性用于区分它们,然后在路由中使用这些名称来选择将要显示的组件。

为了帮助演示这个特性,我通过在src/components文件夹中添加一个名为SideBySide.vue的文件来创建一个新组件,其内容如清单 23-15 所示。

<template>
    <div class="container-fluid">
        <div class="row">
            <div class="col text-center m-2">
                <h3 class="bg-secondary text-white text-center p-2">Left View</h3>
                <router-view name="left" class="border border-secondary p-2" />
            </div>
            <div class="col text-center m-2">
                <h3 class="bg-secondary text-white text-center p-2">Right View</h3>
                <router-view name="right" class="border border-secondary p-2" />
            </div>
        </div>
    </div>
</template>

Listing 23-15The Contents of the SideBySide.vue File in the src/components Folder

这个组件有一个包含两个router-view元素的模板元素,使用name属性区分这两个元素,一个命名为left,另一个命名为right。引导类和结构元素将向用户并排展示router-view元素的内容。为了瞄准新的router-view元素,我将添加对表 23-5 中描述的 URL 的支持。

表 23-5

命名路由器视图元素的 URL

|

统一资源定位器

|

描述

| | --- | --- | | /named/tableleft | 这个 URL 将在left元素中显示产品表,在right元素中显示编辑器。 | | /named/tableright | 这个 URL 将在right元素中显示产品表,在 lef t元素中显示编辑器。 |

为了定位这些 URL,我将清单 23-16 中所示的导航元素添加到顶级App组件的模板中。

<template>
    <div class="container-fluid">
        <div class="row">
            <div class="col text-center m-2">
                <div class="btn-group">
                    <router-link tag="button" to="/products"
                        active-class="btn-info" class="btn btn-primary">
                            Products
                    </router-link>
                    <router-link tag="button" to="/preferences"
                        active-class="btn-info" class="btn btn-primary">
                            Preferences
                    </router-link>
                    <router-link to="/named/tableleft" class="btn btn-primary"

                            active-class="btn-info">

                        Table Left

                    </router-link>

                    <router-link to="/named/tableright" class="btn btn-primary"

                            active-class="btn-info">

                        Table Right

                    </router-link>

                </div>
            </div>
        </div>
        <div class="row">
            <div class="col m-2">
                <router-view></router-view>
            </div>
        </div>
    </div>
</template>

<script>

    export default {
        name: 'App',
        created() {
            this.$store.dispatch("getProductsAction");
        }
    }
</script>

Listing 23-16Adding Navigation Elements in the App.vue File in the src Folder

为了完成对新 URL 的支持并以命名的router-view元素为目标,我创建了清单 23-17 中所示的路由。

import Vue from "vue";
import VueRouter from "vue-router";

import ProductDisplay from "../components/ProductDisplay";
import ProductEditor from "../components/ProductEditor";
import Preferences from "../components/Preferences";
import Products from "../components/Products";

import SideBySide from "../components/SideBySide";

Vue.use(VueRouter);

export default new VueRouter({
    mode: "history",
    routes: [
        { path: "/preferences", component: Preferences},
        { path: "/products", component: Products,
            children: [
                { name: "table", path: "list", component: ProductDisplay},
                { name: "editor", path: ":op(create|edit)/:id(\\d+)?",
                      component: ProductEditor},
                { path: "", redirect: "list" }
            ]
        },
        { path: "/edit/:id", redirect: to => `/products/edit/${to.params.id}`},

        { path: "/named", component: SideBySide,

            children:[

                {   path: "tableleft",

                    components: {

                        left: ProductDisplay,

                        right: ProductEditor

                    }

                },

                {   path: "tableright",

                        components: {

                        left: ProductEditor,

                        right: ProductDisplay

                    }

                }

            ]

        },

        { path: "*", redirect: "/products" }
    ]
})

Listing 23-17Adding Routes in the index.js File in the src/router Folder

当定义以命名的router-view元素为目标的路由时,使用components属性。该属性被赋予一个对象,其属性是router-view元素的名称,其值是应该显示的组件,如下所示:

...
{ path: "tableleft",
  components: {

      left: ProductDisplay,

      right: ProductEditor

  }

},
...

components属性告诉 Vue 路由器包在名为leftrouter-view元素中显示ProductComponent,在名为rightrouter-view元素中显示ProductEditor组件。

注意

以命名元素为目标的属性是components(复数),而不是在清单 23-17 中的其他路径中使用的component(单数)属性。

要查看结果,导航到http://localhost:8080并点击左侧表格和右侧表格按钮,这两个按钮指向清单 23-17 中定义的路线的 URL,产生如图 23-11 所示的结果。

img/465686_1_En_23_Fig11_HTML.jpg

图 23-11

使用命名路由器视图元素

摘要

在本章中,我描述了在 Vue.js 应用中使用 URL 路由时可用的一些高级功能。我解释了如何配置router-link元素以产生不同的 HTML 元素并响应不同的事件,以及如何设计导航元素的样式以向用户提供反馈。对于router-view元素,我向您展示了如何使用嵌套路由,以便应用可以包含多个元素,以及当这些元素在同一个模板中定义时如何命名它们。在下一章,我将描述高级 URL 路由特性。