Vue 入门

Vue 入门

0. 概述

Vue

Vue(读音 /vju/,类似于 view)是一套用于构建用户界面的渐进式框架,发布于2014年2月。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库(如:vue-router,vue-resource,vuex)或既有项目整合。

MVVM 模式的实现者

  • Model:模型层,在这里表示 JavaScript 对象

  • View:视图层,在这里表示 DOM(HTML 操作的元素)

  • ViewModel:连接视图和数据的中间件。Vue.js 就是 MVVM 中的 ViewModel 层的实现者。

在 MVVM 架构中,是不允许数据和视图直接通信的,只能通过 ViewModel 来通信。而 ViewModel 就是定义了一个 Observer 观察者

  • ViewModel 能够观察到数据的变化,并对视图对应的内容进行更新

  • ViewModel 能够监听到视图的变化,并能够通知数据发生改变

至此,我们就明白了:Vue.js 就是一个 MVVM 的实现者。他的核心就是实现了 DOM 监听与数据绑定。

为什么要使用 MVVM

MVVM 模式和 MVC 模式一样,主要目的是分离视图(View)和模型(Model),有几大好处:

  • 低耦合:视图(View)可以独立于 Model 变化和修改。一个 ViewModel 可以绑定到不同的 View 上,当 View 变化的时候 Model 可以不变,当 Model 变化的时候 View 也可以不变。
  • 可复用:你可以把一些视图逻辑放在一个 ViewModel 里面,让很多 View 重用这段视图逻辑。
  • 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
  • 可测试:界面素来是比较难于测试的,而现在测试可以针对 ViewModel 来写。

为什么要使用 Vue.js

  • 轻量级,体积小是一个重要指标。Vue.js 压缩后有只有20多 kb(Angular 压缩后56kb+,React 压缩后44kb+)。
  • 移动优先。更适合移动端,比如移动端的 Touch 事件。
  • 易上手,学习曲线平稳,文档齐全。
  • 吸取了 Angular(模块化)和 React(虚拟DOM)的长处,并拥有自己独特的功能,如:计算属性。
  • 开源,社区活跃度高。

1. 第一个 Vue 应用

尝试 Vue.js 最简单的方法是使用 Hello World 例子。我们可以创建一个 .html 文件,然后通过如下方式引入 Vue:

<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

以上是开发环境版本。如果想要舍弃命令行警告来换取加载速度,也可以选择生产环境版本:

<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>

Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统。我们可以编写这样一个页面来测试:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!-- View层 -->
<div id="app">
    {{message}}
</div>
<!-- 1.导入vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    let vm = new Vue({
        el: "#app",
        //Model层
        data:{
            message: "Hello, Vue!"
        }
    });
</script>
</body>
</html>

在浏览器中打开页面,可以看到页面中输出了 “Hello, Vue!” 这几个字符。

这就是我们的第一个 Vue 应用了。看起来这跟渲染一个字符串模板非常类似,但是 Vue 在背后做了大量工作。现在数据和 DOM 已经被建立了关联,所有东西都是响应式的。我们要怎么确认呢?打开你的浏览器的 JavaScript 控制台 (就在这个页面打开),并修改 vm.message 的值,你将看到上例相应地更新。

页面显示:Hello, Vue!
控制台输入:vm.message="abcd"
页面显示:abcd

注意我们不再和 HTML 直接交互了。一个 Vue 应用会将其挂载到一个 DOM 元素上(对于这个例子是 #vm)然后对其进行完全控制。那个 HTML 是我们的入口,但其余都会发生在新创建的 Vue 实例内部。

2. Vue 基本语法

2.1 v-bind

除了 1. 中代码展示的文本插值,我们还可以像这样来绑定元素 attribute:

<div id="app2">
    <input type="button" v-bind:value="message">
</div>
<script>
    let vm2 = new Vue({
        el: "#app2",
        //Model层
        data:{
            message: "Show the message."
        }
    });
</script>

在浏览器中打开页面,可以看到页面中出现了一个按钮,按钮的标题为 “Show the message.” 现在我们打开浏览器的控制台,输入如下内容:

vm2.message="new name"

可以看到,按钮的标题立刻就变成了 “new name”。

这里我们遇到了一点新东西。你看到的 v-bind attribute 被称为指令。指令带有前缀 v-,以表示它们是 Vue 提供的特殊 attribute。可能你已经猜到了,它们会在渲染的 DOM 上应用特殊的响应式行为。在这里,该指令的意思是:“将这个元素节点的 value attribute 和 Vue 实例的 message property 保持一致”。

2.2 v-if / v-else-if / v-else

v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。同时,也可以用 v-else 添加一个 “else 块”。如下例所示,使用 Vue 之后,控制切换一个元素的内容是否显示其实也相当简单:

<div id="app">
    <h1 v-if="show">你现在看得到我</h1>
    <h1 v-else></h1>
</div>
<script>
    let vm = new Vue({
        el: "#app",
        data:{
            show: true
        }
    });
</script>

当我们在浏览器中打开此页面,并在控制台中输入 “vm.show=false” 后,即可发现:<h1> 标签中的文字消失了!

这个例子演示了我们不仅可以把数据绑定到 DOM 文本或属性,还可以绑定到 DOM 结构。此外,Vue 也提供一个强大的过渡效果系统,可以在 Vue 插入/更新/移除元素时自动应用过渡效果。

2.3 v-for

我们可以用 v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名。在 v-for 块中,我们可以访问所有父作用域的 property。v-for 还支持一个可选的第二个参数,即当前项的索引。

如下例所示,使用 v-for 指令可以绑定数组的数据来渲染一个项目列表:

<div id="app">
    <ol>
        <li v-for="(army, index) in armies">{{army.element}}——{{index}}</li>
    </ol>
</div>
<script>
    let vm = new Vue({
        el: "#app",
        data:{
            armies: [
                {element: "风"},
                {element: "林"},
                {element: "火"},
                {element: "山"},
                {element: "阴"}
            ]
        }
    });
</script>

在浏览器中打开,可以看到页面中显示出了如下的字样:

1.风——0
2.林——1
3.火——2
4.山——3
5.阴——4

此时,如果我们在控制台中输入 vm.armies.push({element:"雷"}),那么就可以看到页面中显示的文字新增了一条:

1.风——0
2.林——1
3.火——2
4.山——3
5.阴——4
6.雷——5

2.4 v-on

使用 v-on 指令可以监听 DOM 事件,并在触发时运行一些 JavaScript 代码。

示例:

<div id="app">
    <button v-on:click="sayHello">Click Me</button>
</div>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            message: "欢迎使用 Vue!"
        },
        methods: {
            //方法必须定义在Vue的methods对象中
            sayHello: function () {
                alert(this.message);
            }
        }
    });
</script>

在浏览器中打开页面,可以看到一个按钮,标题是 “Click Me”。点击按钮,会弹出一个消息提示框,上面写着 “欢迎使用 Vue!”

2.5 v-model

使用 v-model 指令可以在 <input><checkbox><select>表单元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。事实上, v-model 本质不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

示例 <input>

<div id="app">
    请输入文本:<input type="text" v-model="message"> {{message}}
</div>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            message: ""
        }
    });
</script>

打开浏览器进行测试,可以看到页面中有一个文本框。在其中输入 “123456”,每敲一个字符,文本框的右边就会相应地多一个字符出来。接着,打开控制台,输入 vm.message="121212",可以发现,文本框中的文本与其后面的文本均立刻变为了 “121212”。

示例 <select>

<div id="app2">
    <select v-model="selected">
        <option disabled value="">--请选择--</option>
        <option>A</option>
        <option>B</option>
        <option>C</option>
    </select>
    你的选择是:{{selected}}
</div>
<script>
    let vm2 = new Vue({
        el: "#app2",
        data: {
            selected: ""
        }
    });
</script>

经测试不难发现,如果 v-model 表达式的初始值未能匹配任何选项,<select> 元素将被渲染为“未选中”状态。在 iOS 中,这会使用户无法选择第一个选项。因为这样的情况下,iOS 不会触发 change 事件。因此,更推荐像上面这样提供一个值为空的禁用选项

3. 组件

组件是可复用的 Vue 实例,通过 Vue.component 进行全局注册,带有一个名字。我们可以在一个通过 new Vue 创建的 Vue 根实例中,把这个组件作为自定义元素来使用。Props 属性则使你可以在组件上注册一些自定义的属性。当一个值传递给 props 的时候,它就变成了那个组件实例的一个属性。一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。我们可以使用 v-bind 来动态传递 prop,这在我们一开始不清楚要渲染的具体内容的时候非常有用。

示例:

<div id="app">
    <li-diy v-for="item in items" v-bind:args="item.weapon"></li-diy>
</div>
<script>
    Vue.component('li-diy', {
        props: ["args"],
        template: '<li>{{args}}</li>'
    });

    let vm = new Vue({
        el: "#app",
        data: {
            items: [
                {weapon: "开天辟地斧"},
                {weapon: "抽魂打魄鞭"},
                {weapon: "引魑招魅幡"}
            ]
        }
    });
</script>

在浏览器中打开页面,可以看到页面上显示出了如下文字:

● 开天辟地斧
● 抽魂打魄鞭
● 引魑招魅幡

4. Axios

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

使用前,需要先在页面中导入 Axios:

<!-- 导入Axios的CDN -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

示例(GET请求):

data.json:

{
  "name":"惟妙惟霄",
  "url": "https://blog.csdn.net/whlwtj",
  "page": 1,
  "isNonProfit":true,
  "address": {
    "street": "龙蟠路",
    "city":"江苏省南京市",
    "country": "中国"
  },
  "links": [
    {
      "name": "B站",
      "url": "https://www.bilibili.com/"
    },
    {
      "name": "4399",
      "url": "https://www.4399.com/"
    },
    {
      "name": "百度",
      "url": "https://www.baidu.com/"
    }
  ]
}

demo.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <div>{{info.name}}</div>
    <div>{{info.address.street}}</div>
    <a v-bind:href="info.url">CSDN博客</a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 导入Axios的CDN -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    let vm = new Vue({
        el: "#app",
        data(){
            return{
                info: {
                    name: null,
                    address: {
                        street: null,
                        city: null,
                        country: null
                    },
                    url: null
                }
            }
        },
        mounted(){
            //钩子函数,链式编程,ES6新特性
            // ../ 代表返回上一级目录
            axios.get("../data.json").then(response=>(this.info=response.data));
        }
    });
</script>
</body>
</html>

最终页面输出结果如下:

惟妙惟霄
龙蟠路
CSDN博客(这是一个链接,点进去可以进入我的CSDN博客)

5. 计算属性

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。所以,对于任何复杂逻辑,你都应当使用计算属性

计算属性的重点突出在属性两个字上(属性是名词):首先它是个属性,其次这个属性有计算的能力(计算是动词),这里的计算就是个函数。简单点说,它就是一个能够将计算结果缓存起来的属性(将行为转化成了静态的属性)。

示例:

<div id="app">
    <div>currentTime1:{{currentTime1()}}</div>
    <div>currentTime2:{{currentTime2}}</div>
</div>
<script>
    let vm = new Vue({
        el: "#app",
        data: {
            message: "hello",
        },
        methods: {
            currentTime1: function () {
                return Date.now();  //返回一个时间戳
            }
        },
        computed: {
            //计算属性,与methods中的方法不能重名,否则只会调用methods
            currentTime2: function () {
                this.message;
                return Date.now();  //返回一个时间戳
            }
        }
    });
</script>

在浏览器中打开页面,可以看到如下输出结果:

currentTime1:1622343717241
currentTime2:1622343717241

接着,打开控制台,进行如下输入:

vm.currentTime1()

控制台返回的输出结果为:

1622343718431

可以看到,返回值已经发生了改变。而当我们在控制台中进行如下输入时:

vm.currentTime2

控制台返回的结果仍然为:

1622343717241

由此可见,计算属性的值被存入了缓存中,并没有发生变化。

这就是方法与计算属性的区别:计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 currentTime2 中的 message 还没有发生改变,多次访问 currentTime2 计算属性只会立即返回之前的计算结果,而不会再次执行函数。

说明:

  • methods:定义方法,调用方法使用 currentTime1()需要带括号
  • computed:定义计算属性,调用属性使用 currentTime2不需要带括号。通过改变 this.message 可以观察到 currentTime2 的数据变化:即,当方法中的值发生了变化时,缓存就会刷新。例如,当我们在控制台中输入 vm.message="abc"后,再次输出 currentTime2 就可以看到值发生了变化。

6. 插槽

有些时候,我们需要在组件中预留出一些位置,来存放一些未定的内容。此时,就可以使用 <slot> 标签,也就是插槽,来起到一个占位符的作用。

示例:

<div id="app">
    <todo slot="todo">
        <!-- 根据name填充对应的插槽 -->
        <todo-title slot="todo-title" v-bind:title="title"></todo-title>
        <todo-items slot="todo-items" v-for="item in items" v-bind:item="item"></todo-items>
    </todo>
</div>
<script>
    Vue.component("todo",{
        template:
            '<div>\
                <!-- 设置插槽 -->\
                <slot name="todo-title"></slot>\
                <ul>\
                    <slot name="todo-items"></slot>\
                </ul>\
             </div>'
    });
    Vue.component("todo-title",{
        props: ["title"],
        template: '<div>{{title}}</div>'
    });
    Vue.component("todo-items",{
        props: ["item"],
        template: '<li>{{item}}</li>'
    });

    let vm = new Vue({
        el: "#app",
        data: {
            title: "军争",
            items: [
                "其疾如风",
                "其徐如林",
                "侵略如火",
                "不动如山",
                "难知如阴",
                "动如雷震"
            ]
        }
    });
</script>

在浏览器中打开页面,可以看到如下输出结果:

军争
● 其疾如风
● 其徐如林
● 侵略如火
● 不动如山
● 难知如阴
● 动如雷震

7. 自定义事件内容分发

6. 的例子中,如果我们想要实现在每一项旁边增加一个按钮,点击后就删除该项的功能,那么我们多半会这么写:

<div id="app">
    <todo slot="todo">
        <!-- 根据name填充对应的插槽 -->
        <todo-title slot="todo-title" v-bind:title="title"></todo-title>
        <todo-items slot="todo-items" v-for="(item, index) in items" :item="item" :index="index"></todo-items>
    </todo>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    Vue.component("todo",{
        template:
            '<div>\
                <!-- 设置插槽 -->\
                <slot name="todo-title"></slot>\
                <ul>\
                    <slot name="todo-items"></slot>\
                </ul>\
             </div>'
    });
    Vue.component("todo-title",{
        props: ["title"],
        template: '<div>{{title}}</div>'
    });
    Vue.component("todo-items",{
        props: ["item", "index"],
        template: '<li>{{index}}---{{item}} <button @click="remove">删除</button></li>',
        methods: {
            remove: function () {
                //希望能够在这里调用removeItem()
            }
        }
    });
    let vm = new Vue({
        el: "#app",
        data: {
            title: "军争",
            items: [
                "其疾如风",
                "其徐如林",
                "侵略如火",
                "不动如山",
                "难知如阴",
                "动如雷震"
            ]
        },
        methods: {
            removeItem: function (index) {
                this.items.splice(index,1);
            }
        }
    });
</script>

在浏览器中打开页面,可以看到如下输出结果:

军争
● 0---其疾如风 [删除(按钮)]
● 1---其徐如林 [删除(按钮)]
● 2---侵略如火 [删除(按钮)]
● 3---不动如山 [删除(按钮)]
● 4---难知如阴 [删除(按钮)]
● 5---动如雷震 [删除(按钮)]

此时,点击删除按钮是没有用的。不过,当我们在控制台中输入 vm.removeItem(0) 时,“其疾如风”这一项还是被我们成功删掉了的。现在,我们所希望的,就是当我们点击按钮,调用 remove() 方法的时候,可以在该方法中去调用 removeItem() 方法

想要实现这一点,就需要使用自定义事件了。自定义事件的步骤如下:

  1. 父组件可以使用 v-on 声明自定义事件
  2. 子组件可以使用 props 接收父组件传递来的参数。
  3. 子组件可以使用 $emit() 方法触发父组件的自定义事件。其中第一个参数为父组件 v-on 定义的时机,第二个参数为子组件 props 接收到的参数。

示例:

<div id="app">
    <todo slot="todo">
        <!-- 根据name填充对应的插槽 -->
        <todo-title slot="todo-title" v-bind:title="title"></todo-title>
        <todo-items slot="todo-items" v-for="(item, index) in items" :item="item" :index="index" v-on:remove_func="removeItem(index)"></todo-items>
    </todo>
</div>
<script>
    Vue.component("todo",{
        template:
            '<div>\
                <!-- 设置插槽 -->\
                <slot name="todo-title"></slot>\
                <ul>\
                    <slot name="todo-items"></slot>\
                </ul>\
             </div>'
    });
    Vue.component("todo-title",{
        props: ["title"],
        template: '<div>{{title}}</div>'
    });
    Vue.component("todo-items",{
        props: ["item", "index"],
        template: '<li>{{index}}---{{item}} <button @click="remove">删除</button></li>',
        methods: {
            remove: function (index) {
                this.$emit('remove_func', index);
            }
        }
    });

    let vm = new Vue({
        el: "#app",
        data: {
            title: "军争",
            items: [
                "其疾如风",
                "其徐如林",
                "侵略如火",
                "不动如山",
                "难知如阴",
                "动如雷震"
            ]
        },
        methods: {
            removeItem: function (index) {
                this.items.splice(index,1);
            }
        }
    });
</script>

在浏览器中打开页面,点击每一项旁边的按钮,对应的列表项果然被删掉了。并且,列表项前端的序号也自动地发生了改变。

8. Vue-CLI

8.1 什么是 Vue-CLI

Vue-CLI 是官方提供的一个脚手架,用于快速生成一个 vue 的项目模板。它可以预先定义好目录结构及基础代码,就像在创建 Maven 项目时可以选择创建一个骨架项目,这个骨架项目就是脚手架,可以使我们的开发更加的快速。

主要的功能:

  • 统一的目录结构
  • 本地调试
  • 热部署
  • 单元测试
  • 集成打包上线

8.2 准备工作

想要使用 Vue-CLI,需要 node.js 的支持:node.js 官方下载链接

需要注意的是,自 14.0.0 之后的版本都不再支持 Win 7。因此,Win 7 用户请移步历史版本进行下载:node.js 官方历史版本

除此之外,我们最好再去配置一个淘宝镜像 cnpm,以备不时之需:

npm install cnpm -g

不过,淘宝镜像在打包时有时会出现问题,因此还是推荐优先使用 npm,如果 npm 的下载速度过于缓慢,再换用 cnpm

接下来,我们就可以通过 cnpm 来安装 Vue-CLI 了:

cnpm install vue-cli -g

安装完成之后,可以通过 vue list 命令查看 Vue-CLI 为我们提供了哪些创建项目的模板:

browserify
browserify-simple
pwa
simple
webpack
webpack-simple

通常情况下,我们会选择使用 webpack,它可以帮助我们在打包时把基于 ES6 的工程降级为 ES5 的版本。

8.3 第一个 Vue-CLI 程序

  1. 在一个空闲位置创建一个文件夹作为改程序的目录

  2. 以管理员身份打开 cmd,通过 cd 命令进入该文件夹,并输入以下命令

    vue init webpack 项目名称
    

    接着,跟着指示对项目进行相关配置即可,如项目名称、项目描述、作者信息等等。遇到 Y/N 的选择时,统一选 NO。

  3. 进入新生成的目录

    cd 项目名称
    
  4. 安装相关依赖环境

    npm install
    

    如果安装遇到了错误,只需输入提示的改错命令即可。

  5. 启动项目

    npm run dev
    
  6. 如果想要停止项目的运行,只需在 cmd 窗口中按下 ctrl+c,并在 Y/N 中选择 Y 即可。

  7. 以管理员身份启动 IDEA 并 open 这个项目文件夹,然后在 Terminal 中输入 npm run dev,同样可以完成项目的启动。

8.4 Webpack

webpack 可以帮助我们把基于 ES6 开发的应用程序打包成可以在 ES5 浏览器中运行的网页。

8.4.1 安装

安装方法:

npm install webpack -g
和
npm install webpack-cli -g

测试是否安装成功:

webpack -v
或
webpack-cli -v
8.4.2 配置

创建 webpack.config.js 配置文件:

  • entry:入口文件,指定 WebPack 用哪个文件作为项目的入口
  • output:输出,指定 WebPack 把处理完成的文件放置到指定路径
  • module:模块,用于处理各种类型的文件
  • plugins:插件,如热更新、代码重用等
  • resolve:设置路径指向
  • watch:监听,用于设置文件改动后直接打包
8.4.3 使用步骤
  1. 创建一个空项目

  2. 创建一个名为 modules 的目录,用于放置 JS 模块等资源文件

  3. 在 modules 下创建模块文件,如 hello.js,用于编写 JS 模块相关代码

    //暴露一个方法
    exports.sayHi = function () {
        document.write("<h1>欢迎</h1>");
    }
    
  4. 在 modules 下创建一个名为 main.js 的入口文件,用于打包时设置 entry 属性

    let hello = require("./hello");
    hello.sayHi();
    
  5. 在项目目录下创建 webpack.config.js 配置文件

    module.exports = {
        entry: './modules/main.js',
        output: {
            filename: './js/bundle.js'
        }
    };
    
  6. 在 Terminal 中使用 webpack 命令打包

  7. 在项目目录下创建 index.html 文件,并在其中导入打包后的 js 文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <!-- 前端的模块化开发 -->
    <script src="./dist/js/bundle.js"></script>
    </body>
    </html>
    
  8. 在浏览器中打开页面,可以看到如下输出结果:

    欢迎
    

9. Vue Router

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能主要有:

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的 css class 的链接
  • HTML5 历史模式或 hash 模式,在 IE9 中自动降级
  • 自定义的滚动条行为

9.1 安装

Vue Router 是一个插件包,需要进入某个项目中进行安装。可以在 cmd 中通过 cd 进入某个项目路径进行安装,也可以在 IDEA 中打开项目,然后在 Terminal 中直接安装。安装方法如下:

npm install vue-router --save-dev

如果安装出错,只需按照提示执行相应的改错命令即可。

9.2 测试

  1. 8.3 的代码基础上,删除 src 目录中多余的文件,留下 App.vuemain.js 即可。

  2. 在 Components 目录下编写一个 Content.vue 组件:

    <template>
      <h1>内容页</h1>
    </template>
    
    <script>
        export default {
            name: "Content"
        }
    </script>
    
    <style scoped>
    
    </style>
    
  3. src 目录中新建一个 router 文件夹,用于存放路由,并在其中新建一个 index.js 文件:

    import Vue from 'vue';
    import VueRouter from "vue-router";
    import Content from '../components/Content'
    import Main from '../components/main'
    
    //安装路由
    Vue.use(VueRouter);
    
    //配置导出路由
    export default new VueRouter({
      routes: [
        {
          //路由路径
          path: '/content',
          name: 'content',
          //跳转到组件
          component: Content
        },
        {
          //路由路径
          path: '/main',
          name: 'main',
          //跳转到组件
          component: Main
        }
      ]
    });
    
  4. main.js 中配置路由:

    import Vue from 'vue'
    import App from './App'
    import router from './router'   //自动扫描路径里的路由配置
    
    Vue.config.productionTip = false
    
    new Vue({
      el: '#app',
      //配置路由
      router,
      components: { App },
      template: '<App/>'
    });
    
  5. App.vue 中使用路由

    <template>
      <div id="app">
        <h1>Vue-Router</h1>
        <router-link to="/main">首页</router-link>
        <router-link to="/content">内容页</router-link>
        <router-view></router-view>
      </div>
    </template>
    
    <script>
    export default {
      name: 'App',
    }
    </script>
    
    <style>
    #app {
      font-family: 'Avenir', Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    
  6. 启动测试

    npm run dev
    

10. Element UI

10.1 创建工程

  1. 创建一个名为 element-ui-study 的工程:

    vue init webpack element-ui-study
    
  2. 安装依赖

    # 进入工程目录
    cd element-ui-study
    # 安装 vue-router
    npm install vue-router --save-dev
    # 安装 element-ui
    npm i element-ui -S
    # 安装依赖
    npm install
    # 安装 SASS 加载器
    cnpm install sass-loader
    # 启动测试
    npm run dev
    

10.2 测试

  1. 启动 IDEA 并 open 此项目文件夹

  2. 删除 src 目录中多余的文件,留下 App.vuemain.js 即可

  3. src 目录下创建 router 文件夹和 views 文件夹

  4. views 目录下创建 Main.vue:

    <template>
      <h1>首页</h1>
    </template>
    
    <script>
        export default {
            name: "Main"
        }
    </script>
    
    <style scoped>
    
    </style>
    
  5. views 目录下创建 Login.vue:

    <template>
      <div>
        <el-form ref='loginForm' :model="form" :rules="rules" label-width='80px' class='login-box'>
          <h3 class='login-title'>欢迎 登录</h3>
          <el-form-item label="账号" prop="username">
            <el-input type="text" placeholder="请输入账号" v-model="form.username"/>
          </el-form-item>
          <el-form-item label="密码" prop="password">
            <el-input type="password" placeholder="请输入密码" v-model="form.password"/>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" v-on:click="onSubmit(loginForm)">登录</el-button>
          </el-form-item>
        </el-form>
    
        <el-dialog
          title="温馨提示"
          :visible.sync="dialogVisible"
          width="30%"
          :before-close="handLeClose">
          <span>请输入账号和密码</span>
          <span slot="footer" class="dialog-footer">
            <el-button type="primary" @click="dialogVisible = false">确定</el-button>
          </span>
        </el-dialog>
      </div>
    </template>
    
    <script>
      export default {
        name: "Login",
        data() {
          return {
            form: {
              username: '',
              password: ''
            },
            //表单验证,需要在el-form-item 元素中增加prop 属性
            rules: {
              username: [
                {required: true, message: " 账号不可为空", trigger: 'blur'}
              ],
              password: [
                {required: true, message: " 密码不可为空 ", trigger: 'blur'}
              ]
            },
    //对话框显示和隐藏
            dialogVisible: false
          }
        },
        methods: {
          onSubmit(loginForm) {
            //为表单绑定验证功能
            this.$refs.loginForm.validate((valid) => {
              if (valid) {
                //使用vue-router路由到指定页面,该方式称之为编程式导航
                this.$router.push("/main");
              } else {
                this.dialogVisible = true;
                return false;
              }
            });
          }
        }
      }
    </script>
    
    <style scoped>
      .login-box {
        border: 1px solid #DCDFE6;
        width: 350px;
        margin: 180px auto;
        padding: 35px 35px 15px 35px;
        border-radius: 5px;
        -webkit-border-radius: 5px;
        -moz-border-radius: 5px;
        box-shadow: 0 0 25px #909399;
      }
    
      .login-title {
        text-align: center;
        margin: 0 auto 40px auto;
        color: #303133;
      }
    </style>
    
  6. router 目录下创建 index.js 文件:

    import Vue from 'vue';
    import VueRouter from "vue-router";
    
    import Main from '../views/Main';
    import Login from '../views/Login';
    
    Vue.use(VueRouter);
    
    export default new VueRouter({
      routes: [
        {
          path: '/main',
          component: Main
        },
        {
          path: '/login',
          component: Login
        }
      ]
    });
    
  7. 修改 main.js 文件:

    import Vue from 'vue'
    import App from './App'
    
    import router from './router'
    
    //引入Element-UI
    import Element from 'element-ui'
    import 'element-ui/lib/theme-chalk/index.css';
    
    Vue.use(router);
    Vue.use(Element);
    
    new Vue({
      el: '#app',
      router,   //配置路由
      render: h => h(App)   //Element-UI的配置
    });
    
  8. 修改 App.vue:

    <template>
      <div id="app">
        <router-view></router-view>
      </div>
    </template>
    
    <script>
    export default {
      name: 'App',
    }
    </script>
    
    <style>
    #app {
      font-family: 'Avenir', Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    
  9. 启动测试

    npm run dev
    

    在浏览器地址栏中输入 http://localhost:8080/#/login 即可看到登录界面。任意输入用户名和密码后点击登录,即可跳转到主页。

11. 路由嵌套

借助路由,我们可以很方便地实现页面之间的跳转。不过,有时我们会希望跳转后的页面仍可以继续跳转至其他页面,那么此时我们就可以使用嵌套路由

  1. view 目录下新建一个 user 文件夹,并在其中创建两个 Vue 组件:

    Profile.vue:

    <template>
        <h1>个人信息</h1>
    </template>
    
    <script>
        export default {
            name: "UserProfile"
        }
    </script>
    
    <style scoped>
    
    </style>
    

    List.vue:

    <template>
        <h1>用户列表</h1>
    </template>
    
    <script>
        export default {
            name: "List"
        }
    </script>
    
    <style scoped>
    
    </style>
    
  2. index.js 中注册路由,并将其嵌套进 Main 路由之中:

    import Vue from 'vue';
    import VueRouter from "vue-router";
    
    import Main from '../views/Main';
    import Login from '../views/Login';
    
    import UserProfile from '../views/user/Profile';
    import UserList from '../views/user/List';
    
    Vue.use(VueRouter);
    
    export default new VueRouter({
      routes: [
        {
          path: '/main',
          component: Main,
          //嵌套路由
          children: [
            {
              path: '/user/profile',
              component: UserProfile
            },
            {
              path: '/user/list',
              component: UserList
            }
          ]
        },
        {
          path: '/login',
          component: Login
        }
      ]
    });
    
  3. 修改 Main.vue,令其显示一个导航栏,用于对页面的切换。

    其中,当我们为 <el-menu> 添加 router 属性后,便可在 <el-menu-item> 中通过属性 <index> 来进行页面的跳转显示:

    <template>
      <div>
        <el-row class="tac">
          <el-col :span="4">
            <h5>菜单</h5>
            <el-menu
              default-active="this.$route.path"
              class="el-menu-vertical-demo"
              @open="handleOpen"
              @close="handleClose"
              router >
              <el-menu-item index="/user/profile">
                <i class="el-icon-document"></i>
                <span slot="title">个人信息</span>
              </el-menu-item>
              <el-menu-item index="/user/list">
                <i class="el-icon-setting"></i>
                <span slot="title">用户列表</span>
              </el-menu-item>
            </el-menu>
          </el-col>
          <el-col :span="20">
            <el-main>
              <router-view />
            </el-main>
          </el-col>
        </el-row>
    
      </div>
    </template>
    
    <script>
      export default {
        data() {
          return {
            activeIndex: '/user/profile',
          };
        },
        methods: {
          handleSelect(key, keyPath) {
            console.log(key, keyPath);
          }
        }
      }
    </script>
    
    <style scoped>
    
    </style>
    
  4. 打开浏览器,在地址栏中输入 localhost:8080/#/main 即可看到测试结果。

12. 路由钩子与404

12.1 路由模式

路由模式有两种:

  • hash:路径带 # 符号,如 http://localhost/#/login
  • history:路径不带 # 符号,如 http://localhost/login

想要改变路由模式,只需在 index.js 中做出如下修改即可:

export default new VueRouter({
  mode: 'history',
  routes: [
    {
        //...
    }
  ]
});

12.2 404页面

如果我们在浏览器中输入了一个并不存在的地址,比如 localhost:8080/user/abc,会发现页面显示为一片空白。那么,我们该如何建立一个 404 页面来提醒用户进入了一个不存在的地址呢?

方法其实很简单:

  1. views 目录下建立一个 NotFound.vue 文件:

    <template>
      <div>
        <h1>404 Not Found</h1>
      </div>
    </template>
    
    <script>
        export default {
            name: "NotFound"
        }
    </script>
    
    <style scoped>
    
    </style>
    
  2. index.js 中配置相关路由,并令其路径为 '*'。这样一来,只要进入的路径不在前面配置的路径当中,页面自然就会跳转至 NotFound 了:

    import Vue from 'vue';
    import VueRouter from "vue-router";
    
    import Main from '../views/Main';
    import Login from '../views/Login';
    
    import UserProfile from '../views/user/Profile';
    import UserList from '../views/user/List';
    
    import NotFound from "../views/NotFound";
    
    Vue.use(VueRouter);
    
    export default new VueRouter({
      mode: 'history',
      routes: [
        {
          path: '/main',
          props: true,
          component: Main,
          //嵌套路由
          children: [
            {
              path: '/user/profile',
              component: UserProfile
            },
            {
              path: '/user/list',
              component: UserList
            }
          ]
        },
        {
          path: '/login',
          component: Login
        },
        {
          path: '*',
          component: NotFound
        }
      ]
    });
    
  3. 在浏览器的地址栏中输入 localhost:8080/user/abc,发现页面输出如下:

    404 Not Found
    

12.3 路由钩子

12.3.1 钩子函数
  • beforeRouteEnter:进入路由前自动执行
  • beforeRouteLeave:离开路由前自动执行

示例:

修改 Profile.vue 中 <script> 部分的代码:

<script>
    export default {
        name: "UserProfile",
        beforeRouteEnter:(to,from,next)=>{
            console.log("进入路由前");
            next();
        },
        beforeRouteLeave:(to,from,next)=>{
            console.log("离开路由前");
            next();
        }
    }
</script>

测试:

在浏览器中打开 localhost:8080/main 页面,点击 “个人信息”,控制台输出如下:

进入路由前

点击 “用户列表”,控制台输出如下:

离开路由前

其中,三个参数的作用分别如下:

  • to:路由将要跳转的路径信息
  • from:路径跳转前的路径信息
  • next:路由的控制参数
    • next():跳入下一个页面
    • next(’/path’):改变路由的跳转方向,使其跳到另一个路由
    • next(false):返回原来的页面
    • next((vm)=>0)):仅在 beforeRouteEnter 中可用,vm 是组件实例
12.3.2 在钩子函数中使用异步请求
  1. 安装 Axios 和 Vue-Axios

    cnpm install axios vue-axios --save-dev
    
  2. main.js 中引用 Axios

    import axios from 'axios'
    import VueAxios from 'vue-axios'
    
    Vue.use(VueAxios, axios);
    
  3. static 目录下建立一个 mock 文件夹用于测试,并在其中建立 data.json

    {
      "name":"惟妙惟霄",
      "url": "https://blog.csdn.net/whlwtj",
      "page": 1,
      "isNonProfit":true,
      "address": {
        "street": "龙蟠路",
        "city":"江苏省南京市",
        "country": "中国"
      },
      "links": [
        {
          "name": "B站",
          "url": "https://www.bilibili.com/"
        },
        {
          "name": "4399",
          "url": "https://www.4399.com/"
        },
        {
          "name": "百度",
          "url": "https://www.baidu.com/"
        }
      ]
    }
    
  4. 在浏览器地址栏中输入 http://localhost:8080/static/mock/data.json,可以看到页面中输出了此文件

  5. beforeRouteEnter 中进行异步请求

    <script>
        export default {
            name: "UserProfile",
            beforeRouteEnter:(to,from,next)=>{
              //进入路由前加载数据
              next(vm => {
                vm.getData();
              });
            },
            beforeRouteLeave:(to,from,next)=>{
              console.log("离开路由前");
              next();
            },
            methods: {
              getData: function () {
                this.axios({
                  method: "GET",
                  url: "http://localhost:8080/static/mock/data.json"
                }).then(function (response) {
                  console.log(response);
                });
              }
            }
        }
    </script>
    
  6. 在浏览器地址栏中输入 http://localhost:8080/user/profile,即可在控制台中看到 json 对象的输出结果。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值