VUE课程笔记

VUE课件

一.第一个Vue程序

什么是MVVM

MVVM(Model-View-ViewModel)是一种软件设计模式,由微软WPF(用于替代WinForm,以前就是用这个技术开发桌面应用程序的)和Silverlight(类似于Java Applet,简单点说就是在浏览器上运行WPF)的架构师Ken Cooper和Ted Peters开发,是一种简化用户界面的事件驱动编程方式。由John Gossman(同样也是WPF和Sliverlight的架构师)与2005年在他的博客上发表。

MVVM源自于经典的MVC(Model-View-Controller)模式。MVVM的核心是ViewModel层,负责转换Model中的数据对象来让数据变得更容易管理和使用。其作用如下:

  • 该层向上与视图层进行双向数据绑定
  • 向下与Model层通过接口请求进行数据交互

在这里插入图片描述

MVVM已经相当成熟了,主要运用但不仅仅在网络应用程序开发中。当下流行的MVVM框架有Vue.jsAngular JS

为什么要使用MVVM

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

  • 低耦合:视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。

  • 可复用:你可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。

  • 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewMode),设计人员可以专注于页面设计。

  • 可测试:界面素来是比较难以测试的,而现在测试可以针对ViewModel来写。

    在这里插入图片描述

(1)View

View是视图层, 也就是用户界面。前端主要由HTML和csS来构建, 为了更方便地展现 已经产生了各种各样的前后端模板语言, 比如FreeMarker,Thymeleaf等等, 各大MV VM框架如Vue.js.Angular JS, EJS react等也都有自己用来构建用户界面的内置模板语言。

(2)Model

Model是指数据模型, 泛指后端进行的各种业务逻辑处理和数据操控, 主要围绕数据库系统展开。这里的难点主要在于需要和前端约定统一的接口规则

(3)ViewModel

ViewModel是由前端开发人员组织生成和维护的视图数据层。在这一层, 前端开发者对从后端获取的Model数据进行转换处理, 做二次封装, 以生成符合View层使用预期的视图数据模型。
需要注意的是View Model所封装出来的数据模型包括视图的状态和行为两部分, 而Model层的数据模型是只包含状态的

比如页面的这一块展示什么,那一块展示什么这些都属于视图状态(展示)
页面加载进来时发生什么,点击这一块发生什么,这一块滚动时发生什么这些都属于视图行为(交互)
视图状态和行为都封装在了View Model里。这样的封装使得View Model可以完整地去描述View层。由于实现了双向绑定, View Model的内容会实时展现在View层, 这是激动人心的, 因为前端开发者再也不必低效又麻烦地通过操纵DOM去更新视图。
  MVVM框架已经把最脏最累的一块做好了, 我们开发者只需要处理和维护View Model, 更新数据视图就会自动得到相应更新,真正实现事件驱动编程。
  View层展现的不是Model层的数据, 而是ViewModel的数据, 由ViewModel负责与Model层交互, 这就完全解耦了View层和Model层, 这个解耦是至关重要的, 它是前后端分离方案实施的重要一环。

Vue

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

​ Model:模型层, 在这里表示JavaScript对象
​ View:视图层, 在这里表示DOM(HTML操作的元素)
​ ViewModel:连接视图和数据的中间件, Vue.js就是MVVM中的View Model层的实现者
​ 在MVVM架构中, 是不允许数据和视图直接通信的, 只能通过ViewModel来通信, 而View Model 就是定义了一个Observer观察者

​ ViewModel能够观察到数据的变化, 并对视图对应的内容进行更新
​ ViewModel能够监听到视图的变化, 并能够通知数据发生改变
​ 至此, 我们就明白了, Vue.js就是一个MV VM的实现者, 他的核心就是实现了DOM监听与数据绑定

( 2 )为什么要使用Vue.js

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

第一个Vue程序

(1)下载地址

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>

(2)代码编写

Vue.js的核心是实现了MVVM模式, 她扮演的角色就是View Model层, 那么所谓的第一个应用程序就是展示她的数据绑定功能,操作流程如下:

1、创建一个HTML文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

</body>
</html>

2、引入Vue.js

<!--1.导入Vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>

3、创建一个Vue实例

<script type="text/javascript">
    var vm = new Vue({
        el:"#app",
        /*Model:数据*/
        data:{
            message:"hello,vue!"
        }
    });
</script>

  • el: '#vue':绑定元素的ID
  • data:{message:'Hello Vue!'}:数据对象中有一个名为message的属性,并设置了初始值 Hello Vue!

4、将数据绑定到页面元素

<!--view层,模板-->
<div id="app">
    {{message}}
</div>

说明:只需要在绑定的元素中使用双花括号将Vue创建的名为message属性包裹起来, 即可实现数据绑定功能, 也就实现了View Model层所需的效果

(3)完整的HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>  

<!--view层,模板-->
<div id="app">
    {{message}}
</div>

<!--1.导入Vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el:"#app",
        /*Model:数据*/
        data:{
            message:"hello,vue!"
        }
    });
</script>
</body>
</html>

(4)测试

为了能够更直观的体验Vue带来的数据绑定功能, 我们需要在浏览器测试一番, 操作流程如下:
  1、在浏览器上运行第一个Vue应用程序, 进入开发者工具
  2、在控制台输入vm.message=‘HelloWorld’, 然后回车, 你会发现浏览器中显示的内容会直接变成HelloWorld
  此时就可以在控制台直接输入vm.message来修改值, 中间是可以省略data的, 在这个操作中, 我并没有主动操作DOM, 就让页面的内容发生了变化, 这就是借助了Vue的数据绑定功能实现的; MV VM模式中要求View Model层就是使用观察者模式来实现数据的监听与绑定, 以做到数据与视图的快速响应。

二 、基础语法指令

2-1、v-bind

我们已经成功创建了第一个Vue应用!看起来这跟渲染一个字符串模板非常类似, 但是Vue在背后做了大量工作。现在数据和DOM已经被建立了关联, 所有东西都是响应式的。我们在控制台操作对象属性,界面可以实时更新!
我们还可以使用v-bind来绑定元素特性!上代码

<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>

<!--view层,模板-->
<div id="app">
    <span v-bind:title="message">
    鼠标悬停几秒钟查看此处动态绑定的提示信息!
  </span>
</div>

<!--1.导入Vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el:"#app",
        /*Model:数据*/
        data:{
            message: '页面加载于 ' + new Date().toLocaleString()
        }
    });
</script>
</body>
</html>

你看到的v-bind等被称为指令。指令带有前缀v以表示它们是Vue提供的特殊特性。可能你已经猜到了, 它们会在渲染的DOM上应用特殊的响应式行为在这里,该指令的意思是:“将这个元素节点的title特性和Vue实例的message属性保持一致”。
  如果你再次打开浏览器的JavaScript控制台, 输入app, message=‘新消息’,就会再一次看到这个绑定了title特性的HTML已经进行了更新。同时在这里我们也要要知道v-bind 的简写模式(:)

2-2、v-if, v-else

什么是条件判断语句,就不需要我说明了吧,以下两个属性!

  • v-if
  • v-else

上代码

<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--view层,模板-->
<div id="app">
    <h1 v-if="ok">Yes</h1>
    <h1 v-else>No</h1>
   
</div>

<!--1.导入Vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el:"#app",
        /*Model:数据*/
        data:{
            type: true
        }
    });
</script>
</body>
</html>

测试:
1.在浏览器上运行,打开控制台!
2.在控制台输入vm.ok=false然后回车,你会发现浏览器中显示的内容会直接变成NO
   注:使用v-*属性绑定数据是不需要双花括号包裹的

v-else-if

  • v-if
  • v-else-if
  • v-else
    注:===三个等号在JS中表示绝对等于(就是数据与类型都要相等)上代码:
<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--view层,模板-->
<div id="app">
    <h1 v-if="type==='A'">A</h1>
    <h1 v-else-if="type==='B'">B</h1>
    <h1 v-else-if="type==='D'">D</h1>
    <h1 v-else>C</h1>

</div>

<!--1.导入Vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el:"#app",
        /*Model:数据*/
        data:{
            type: 'A'
        }
    });
</script>
</body>
</html>

2-3、v-for
  • v-for

格式说明

<div id="app">
    <li v-for="(item,index) in items">
        {{item.message}}---{{index}}
    </li>

</div>

注:items是数组,item是数组元素迭代的别名!
  上代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--view层,模板-->
<div id="app">
    <li v-for="(item,index) in items">
        {{item.message}}---{{index}}
    </li>

</div>

<!--1.导入Vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el:"#app",
        /*Model:数据*/
        data:{
            items:[
                {message:'行思科技 刘老师'},
                {message:'行思科技 夏老师'},
                {message:'行思科技 贾老师'}
            ]
        }
    });
</script>
</body>
</html>

测试:在控制台输入vm.items.push({message:'行思科技 毕老师'}),尝试追加一条数据,你会发现浏览器中显示的内容会增加一条行思科技 毕老师.

2-4、v-on

v-on监听事件
 事件有Vue的事件、和前端页面本身的一些事件!我们这里的click是vue的事件, 可以绑定到Vue中的methods中的方法事件!
 上代码:

<!DOCTYPE html>
<html lang="en" xmlns:v-on="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    <button v-on:click="sayHi">点我</button>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el:"#app",
        data:{
            message:'Hello World'
        },
        methods:{
            sayHi:function(event){
                //'this'在方法里面指向当前Vue实例
                alert(this.message);
            }
        }
    });
</script>
</body>
</html>

并且我们在这学习到了一个新的属性methods 注意看好写法 并不是() 我们在html文档里面写的各种事件都可以存放到这

2-5、v-model

什么是双向数据绑定
Vue.js是一个MV VM框架, 即数据双向绑定, 即当数据发生变化的时候, 视图也就发生变化, 当视图发生变化的时候,数据也会跟着同步变化。这也算是Vue.js的精髓之处了。
  值得注意的是,我们所说的数据双向绑定,一定是对于UI控件来说的非UI控件不会涉及到数据双向绑定。单向数据绑定是使用状态管理工具的前提。如果我们使用vue x那么数据流也是单项的,这时就会和双向数据绑定有冲突。

为什么要实现数据的双向绑定

在Vue.js中,如果使用vuex, 实际上数据还是单向的, 之所以说是数据双向绑定,这是用的UI控件来说, 对于我们处理表单, Vue.js的双向数据绑定用起来就特别舒服了。即两者并不互斥,在全局性数据流使用单项,方便跟踪;局部性数据流使用双向,简单易操作。

在表单中使用双向数据绑定
你可以用v-model指令在表单、及元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇, 但v-model本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
  注意:v-model会忽略所有表单元素的value、checked、selected特性的初始值而总是将Vue实例的数据作为数据来源。你应该通过JavaScript在组件的data选项中声明初始值!

(1)单行文本

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    输入的文本:<input type="text" v-model="message" value="hello">{{message}}
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript ">
    var vm = new Vue({
        el:"#app",
        data:{
            message:""
        }
    });
</script>
</body>
</html>

(2)多行文本

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
   多行文本:<textarea v-model="pan"></textarea>&nbsp;&nbsp;多行文本是:{{pan}}
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el:"#app",
        data:{
            pan:"Hello hello!"
        }
    });
</script>
</body>
</html>

(3)单复选框

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    单复选框:
    <input type="checkbox" id="checkbox" v-model="checked">
    &nbsp;&nbsp;
    <label for="checkbox">{{checked}}</label>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el:"#app",
        data:{
            checked:false
        }
    });
</script>
</body>
</html>

4多复选框

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    多复选框:
    <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
    &nbsp;&nbsp;
    <label for="jack">Jack</label>
    <input type="checkbox" id="join" value="Join" v-model="checkedNames">
    &nbsp;&nbsp;
    <label for="join">Jack</label>
    <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
    &nbsp;&nbsp;
    <label for="mike">Mike</label>
    <span>选中的值:{{checkedNames}}</span>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el:"#app",
        data:{
            checkedNames:[]
        }
    });
</script>
</body>
</html>

(5)单选按钮

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app">
    单选框按钮
    <input type="radio" id="one" value="One" v-model="picked">
    <label for="one">One</label>
    <input type="radio" id="two" value="Two" v-model="picked">
    <label for="two">Two</label>
    <span>选中的值:{{ }}</span>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el:"#app",
        data:{
            picked:'Two'
        }
    });
</script>
</body>
</html>

(6)下拉框

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
<!--    性别:
    <input type="radio" name="sex" value="男" v-model="pan">男
    <input type="radio" name="sex" value="女" v-model="pan">女
    <p>选中了:{{pan}}</p>-->

    下拉框:
    <select v-model="pan">
        <option value="" disabled>---请选择---</option>
        <option>A</option>
        <option>B</option>
        <option>C</option>
        <option>D</option>
    </select>
    <span>value:{{pan}}</span>



</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el:"#app",
        data:{
            pan:"A"
        }
    });
</script>
</body>
</html>

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

三、详解vue生命周期

首先,每一个vue实例都有一个完整的生命周期,也就是从创建、初始化数据、编译模板、挂载Dom、渲染→更新→渲染、销毁等一系列过程,我们称这是Vue的生命周期。通俗说就是Vue实例从创建到销毁的过程,就是生命周期。

这是官方文档上的图片

img

可以看到在vue一整个的生命周期中会有很多钩子函数提供给我们在vue生命周期不同的时刻进行操作, 那么先列出所有的钩子函数,然后我们再一一详解:

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeDestroy
  • destroyed

我们先简单的来解说这张图,然后再通过例子来详看
首先,从图上,我们可以看出,他的一个过程是

  1.  new Vue()实例化一个vue实例,然后init初始化event 和 lifecycle, 其实这个过程中分别调用了3个初始化函数(initLifecycle(), initEvents(), initRender()),分别初始化了生命周期,事件以及定义createElement函数,初始化生命周期时,定义了一些属性,比如表示当前状态生命周期状态得_isMounted ,_isDestroyed ,_isBeingDestroyed,表示keep-alive中组件状态的_inactive,而初始化event时,实际上就是定义了$once、$off、$emit、$on几个函数。而createElement函数是在初始化render时定义的(调用了initRender函数)
    
  2.  执行beforeCreate生命周期函数
    
  3.  beforeCreate执行完后,会开始进行数据初始化,这个过程,会定义data数据,方法以及事件,并且完成数据劫持observe以及给组件实例配置watcher观察者实例。这样,后续当数据发生变化时,才能感知到数据的变化并完成页面的渲染
    
  4.   执行created生命周期函数,所以,当这个函数执行的时候,我们已经可以拿到data下的数据以及methods下的方法了,所以在这里,我们可以开始调用方法进行数据请求了
    
  5.  created执行完后,我们可以看到,这里有个判断,判断当前是否有el参数(这里为什么需要判断,是因为我们后面的操作是会依赖这个el的,后面会详细说),如果有,我们再看是否有template参数。如果没有el,那么我们会等待调用$mount(el)方法(后面会详细说)。
    
  6.  确保有了el后,继续往下走,判断当有template参数时,我们会选择去将template模板转换成render函数(其实在这前面是还有一个判断的,判断当前是否有render函数,如果有的话,则会直接去渲染当前的render函数,如果没有那么我们才开始去查找是否有template模板),如果没有template,那么我们就会直接将获取到的el(也就是我们常见的#app,#app里面可能还会有其他标签)编译成templae, 然后在将这个template转换成render函数。
    
  7.  之后再调用beforMount, 也就是说实际从creted到beforeMount之间,最主要的工作就是将模板或者el转换为render函数。并且我们可以看出一点,就是你不管是用el,还是用template, 或者是用我们最常用的.vue文件(如果是.vue文件,他其实是会先编译成为template),最终他都是会被转换为render函数的。
    
  8.  beforeMount调用后,我们是不是要开始渲染render函数了,首先我们会先生产一个虚拟dom(用于后续数据发生变化时,新老虚拟dom对比计算),进行保存,然后再开始将render渲染成为真实的dom。渲染成真实dom后,会将渲染出来的真实dom替换掉原来的vm.$el(这一步我们可能不理解,请耐心往下看,后面我会举例说明),然后再将替换后的$el append到我们的页面内。整个初步流程就算是走完了
    
  9.  之后再调用mounted,并将标识生命周期的一个属性_isMounted 置为true。所以mounted函数内,我们是可以操作dom的,因为这个时候dom已经渲染完成了。_
    
  10. 再之后,只有当我们状态数据发生变化时,我们在触发beforeUpdate,要开始将我们变化后的数据渲染到页面上了(实际上这里是有个判断的,判断当前的_isMounted是不是为ture并且1_isDestroyed是不是为false,也就是说,保证dom已经被挂载的情况下,且当前组件并未被销毁,才会走update流程)_
    
  11. _beforeUpdate调用之后,我们又会重新生成一个新的虚拟dom(Vnode),然后会拿这个最新的Vnode和原来的Vnode去做一个diff算,这里就涉及到一系列的计算,算出最小的更新范围,从而更新render函数中的最新数据,再将更新后的render函数渲染成真实dom。也就完成了我们的数据更新
    
  12. _然后再执行updated,所以updated里面也可以操作dom,并拿到最新更新后的dom。不过这里我要插一句话了,mouted和updated的执行,并不会等待所有子组件都被挂载完成后再执行,所以如果你希望所有视图都更新完毕后再做些什么事情,那么你最好在mouted或者updated中加一个$nextTick(),然后把要做的事情放在$netTick()中去做(至于为什么,以后讲到$nextTick再说吧)
    
  13. _再之后beforeDestroy没啥说的,实例销毁前,也就是说在这个函数内,你还是可以操作实例的
    
    之后会做一系列的销毁动作,解除各种数据引用,移除事件监听,删除组件_watcher,删除子实例,删除自身self等。同时将实例属性_isDestroyed置为true
  14. 销毁完成后,再执行destroyed
    

示例

大致过程就是这样,下面我们来通过例子来看一看

<body>
    <div id="app">
        <p>{{message}}</p>
        <button @click="changeMsg">改变</button>
    </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script type ='text/javascript'>
    var vm = new Vue({
        el: '#app',
        data: {
            message: 'hello world'
        },
        methods: {
            changeMsg () {
                this.message = 'world'
            }
        },
        beforeCreate() {
            console.log('------初始化前------')
            console.log(this.message)
            console.log(this.$el)
        },
        created () {
            console.log('------初始化完成------')
            console.log(this.message)
            console.log(this.$el)
        },
        beforeMount () {
            console.log('------挂载前---------')
            console.log(this.message)
            console.log(this.$el)
        },
        mounted () {
            console.log('------挂载完成---------')
            console.log(this.message)
            console.log(this.$el)
        },
        beforeUpdate () {
            console.log('------更新前---------')
            console.log(this.message)
            console.log(this.$el)
        },
        updated() {
            console.log('------更新后---------')
            console.log(this.message)
            console.log(this.$el)
        }
    })
</script>

我们先看看首次加载时,输出了啥

在这里插入图片描述

从上面我们可以看出几点,

首次,只执行了4个生命周期,beforeCreate,created, beforeMount, mounted。
同时,我们可以看出,第一个生命周期中,我们拿不到data中的数据,因为这个时候数据还未初始化
created中,我们可以拿到data中的message数据了,因为初始化已经完成
beforeMount中,我们可以看出,我们拿到了 e l ,而 m o u n t e d 中,我们也拿到了 el,而mounted中,我们也拿到了 el,而mounted中,我们也拿到了el, 不过好像有点不一样是吧。一个好像是渲染前的,一个是渲染后的。对的。看过MVVM响应式原来或者Vue源码你们就会发现,最初其实我们是会去让this.KaTeX parse error: Expected 'EOF', got '#' at position 54: …中,其实我们拿到的就是页面中的#̲app。而再继续往后,首先我们…el啊。这也就是我们前面所说到的替换 e l 是什么意思了。所以,在 m o u n t e d 中,我们所得到的渲染完成后的 el是什么意思了。 所以, 在mounted中,我们所得到的渲染完成后的 el是什么意思了。所以,在mounted中,我们所得到的渲染完成后的el。
下面我们再看个例子

var vm = new Vue({
        el: '#app',
        data: {
            message: 'hello world'
        },
        template: '<div>我是模板内的{{message}}</div>',
        methods: {
            changeMsg () {
                this.message = 'goodbye world'
            }
        },
        beforeCreate() {
            console.log('------初始化前------')
            console.log(this.message)
            console.log(this.$el)
        },
        created () {
            console.log('------初始化完成------')
            console.log(this.message)
            console.log(this.$el)
        },
        beforeMount () {
            console.log('------挂载前---------')
            console.log(this.message)
            console.log(this.$el)
        },
        mounted () {
            console.log('------挂载完成---------')
            console.log(this.message)
            console.log(this.$el)
        },
        beforeUpdate () {
            console.log('------更新前---------')
            console.log(this.message)
            console.log(this.$el)
        },
        updated() {
            console.log('------更新后---------')
            console.log(this.message)
            console.log(this.$el)
        }
    })

我们在new Vue实例的时候直接传入了一个template,这时候我们再看输出

在这里插入图片描述

这么看是不是就很清晰了啊 ,在beforeMount的时候,KaTeX parse error: Expected 'EOF', got '#' at position 5: el还是#̲app, 但是在mounted…el。

下面我们删除上面的template, 点击按钮更改下message,查看输出

在这里插入图片描述

哎。。。有没有看到一个很奇怪的东西啊,在beforeUpdate中输出的 e l 居然和 u p d a t e d 里面输出的是一样的。这不对啊,以我们上面所说的逻辑的话, b e f o r e U p d a t e 内的 el居然和updated里面输出的是一样的。这不对啊,以我们上面所说的逻辑的话,beforeUpdate内的 el居然和updated里面输出的是一样的。这不对啊,以我们上面所说的逻辑的话,beforeUpdate内的el应该是更新前的啊。这是怎么回事呢。这时候我们先来看一下mounted里面的。mounted里面我们看到p标签内依旧是hello world 对不对,其实这是因为,我是先点击了#app那个div的箭头,将这个div展开了以后,我再点击的按钮去更改了message,所以mounted里面还是原来的。那我现在如果先不展开mounted里面的div的话,我们来看看会怎么样
在这里插入图片描述

可以看到,初始输出,其实是这样的,我们看不到#app内的东西,需要点击箭头展开才能看到,现在,我不展开,然后我先点击按钮去改变message, 等beforUpdate和updated都执行完成后,我们再来一起展开,看下会怎么样

在这里插入图片描述

这是点击改变了message后的截图,然后我们现在展开div看看

在这里插入图片描述

看到没有,我们发现什么啦,怎么现在mounted里面的 e l 也变成更新后的啦。呵呵,不要慌,其实啊,因为 t h i s . el也变成更新后的啦。 呵呵,不要慌,其实啊,因为this. el也变成更新后的啦。呵呵,不要慌,其实啊,因为this.el是一个对象,其实本质就是一个指针,当我们刚console.log输出的时候,其实并没有显示内容,而当我们点击箭头去展开这个div的时候,将指针指向了当前的 e l ,所以我们看到的才会都是改变后的 el,所以我们看到的才会都是改变后的 el,所以我们看到的才会都是改变后的el。这也就是为什么之前mounted里面的$el是改变之前的值,而现在是改变之后的值了,因为之前那张图,我是先展开了mounted中的div,再去改变的message。下面我们再来验证下是不是这么回事
怎么验证,我们修改下代码

mounted () {
            console.log('------挂载完成---------')
            console.log(this.message)
            console.log(this.$el.innerHTML)
            console.log(this.$el)
        },
        beforeUpdate () {
            console.log('------更新前---------')
            console.log(this.message)
            console.log(this.$el.innerHTML)
            console.log(this.$el)
        },
        updated() {
            console.log('------更新后---------')
            console.log(this.message)
            console.log(this.$el.innerHTML)
            console.log(this.$el)
        }

我们增加一个输出 this.$el.innerHTML, 再查看结果

在这里插入图片描述

这么看是不是就很明了啦,beforeUpdate里面的$el的内容,确实还是改变之前的,而我们之前看到的,只是因为我们后面展开时指针指向了当前值才导致的,是个视觉差而已。

后面两个销毁的,我就不举例说明了,没啥说的。下面我们再看一个问题,就是如果我们没有设置el时,会怎么样,我们在之前的生命周期图中,是说过,当没有找到el时, 说是不是会等待vm.$mount(el) 啊,这句话啥意思,我们来看一下
在这里插入图片描述

首先,我们看下,vue源码中,

在这里插入图片描述

在执行完,beforeCreate和created之后,是做了个判断,当存在el时,调用了 $mount方法,created之后的步骤,就是在这里面去走的。那如果没有el呢, 生命周期图中是说等待vm. $mount调用。那是不是只能等待我们手动去调用啊。

var vm = new Vue({
        data: {
            message: 'hello world'
        },
        // template: '<div>我是模板内的{{message}}  <button @click="changeMsg">点我</button></div>',
        methods: {
            changeMsg () {
                this.message = 'goodbye world'
            }
        },
        beforeCreate() {
            console.log('------初始化前------')
            console.log(this.message)
            console.log(this.$el)
        },
        created () {
            console.log('------初始化完成------')
            console.log(this.message)
            console.log(this.$el)
        },
        beforeMount () {
            console.log('------挂载前---------')
            console.log(this.message)
            console.log(this.$el)
        },
        mounted () {
            console.log('------挂载完成---------')
            console.log(this.message)
            console.log(this.$el.innerHTML)
            console.log(this.$el)
        },
        beforeUpdate () {
            console.log('------更新前---------')
            console.log(this.message)
            console.log(this.$el.innerHTML)
            console.log(this.$el)
        },
        updated() {
            console.log('------更新后---------')
            console.log(this.message)
            console.log(this.$el.innerHTML)
            console.log(this.$el)
        }
    })

这个时候,我们删除了el属性,看看结果

在这里插入图片描述

是不是只走了前面两个生命周期啊,后面就没走了,这个时候其实就是在等 m o u n t 被调用了,那我们加个按钮,点击按钮,手动调用一下 mount被调用了,那我们加个按钮,点击按钮,手动调用一下 mount被调用了,那我们加个按钮,点击按钮,手动调用一下mount看会怎样是不是只走了前面两个生命周期啊,后面就没走了,

这个时候其实就是在等 m o u n t 被调用了,那我们加个按钮,点击按钮,手动调用一下 mount被调用了,那我们加个按钮,点击按钮,手动调用一下 mount被调用了,那我们加个按钮,点击按钮,手动调用一下mount看会怎样

在这里插入图片描述

没点击之前在这里插入图片描述

点击后

在这里插入图片描述

可以看到,生命周期继续往下走了。
这时候不知道大家是不是想起来,看到有些vue项目的main.js里面是这样的

export default new Vue({
  el: '#app',
  router,
  store,
  i18n,
  render: h => h(App)
})

而有些vue项目中人家用的又是这样的

export default new Vue({
  router,
  store,
  i18n,驱蚊器
  render: h => h(App)
}).$mount('#app')

其实后者,就相当于是手动调用了$mount了。

四、vue的计算属性 与 监听属性

1、什么是计算属性

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

首先,来看一个字符串反转的例子:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>Title</title>
	</head>
	<body>
		<!--view层,模板-->
		<div id="app1">
			{{ message.split('').reverse().join('') }}
		</div>
		<!--1.导入Vue.js-->
		<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>


		<script>
			new Vue({
				el: "#app1",
				data: {
					message: 'xiaoqi'
				}
			})
		</script>
	</body>
</html>



上面这个例子,在模板中表达式包括3个操作,相对比较复杂,也不容易看懂。

所以在遇到复杂的逻辑时应该使用计算属性。

将上例进行改写:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>Title</title>
	</head>
	<body>
		<!--view层,模板-->
		<div id="app2">
			<p>原字符串:{{message}}</p>
			<p>计算后反转字符串: {{reverseMessage}}</p>
		</div>
		<!--1.导入Vue.js-->
		<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
		<script>
			new Vue({
				el: "#app2",
				data: {
					message: 'xiaoqi'
				},
				computed: {
					// 计算属性的getter
					reverseMessage: function() {
						return this.message.split('').reverse().join('');
					}
				}
			})
		</script>
	</body>
</html>

上面的模板中声明了一个计算属性reverseMessage。
提供的函数将用作属性reverseMessage的getter(用于读取)。
reverseMassage依赖于massage,在massage发生变化时,reverseMassage也会更新。

[ computed Vs methods]
我们发现computed属性完全可以由methods属性所代替,效果时完全一样的。既然使用methods就可以实现,那么为什么还需要计算属性呢?原因就是computed是基于它的依赖缓存,只有相关依赖发生改变时,才会重新取值。而使用methods,在重新渲染的时候,函数总会重新调用执行。可以说使用computed性能会更好。

[ 依赖缓存]
举一个更好说明computed是基于依赖缓存的例子:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>Title</title>
	</head>
	<body>
		<!--view层,模板-->
	<div id="app3">
	        <p>原字符串:{{message}}</p>
	        <p>计算反转字符串:{{reverseMessage1}}</p>
	        <p>计算反转字符串:{{reverseMessage1}}</p>
	        <p>计算反转字符串:{{reverseMessage2()}}</p>
	        <p>计算反转字符串:{{reverseMessage2()}}</p>
	</div>
		<!--1.导入Vue.js-->
		<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
	
		<script>
		  var num = 1;
		    new Vue({
		        el: "#app3",
		        data: {
		            message: 'xiaoqi'
		        },
		        computed: {
		            reverseMessage1:function () {
		                num += 1;
		                return num +  this.message.split('').reverse().join('');
		            }
		        },
		        methods: {
		            reverseMessage2() {
		                num += 1;
		                return num + this.message.split('').reverse().join('');
		            }
		        }
		        
		    })
		</script>

	</body>
</html>

这个例子中,num是一个独立的变量。在使用reverseMessage1这个计算属性时,num会变成2 。但是当再使用reverseMessage1属性时,num没有变化,依然是2。因为Vue实例的message数据没有发生变化 于是DOM渲染就直接用这个值,不会重复执行代码。而reverseMessage2这个方法只要用一个,就要执行一次,于是每次返回的结果都不一样。
[computed setter]
每一个计算属性都包含一个getter与一个setter,上面的实例中都是计算属性的默认用法,只是利用getter来读取。
computed属性默认只有getter,不过在需要时可以自己提供一个setter函数,当手动修改计算属性的值时,就会触发setter函数,执行自定义的操作。
例如:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>Title</title>
	</head>
	<body>
		<!--view层,模板-->
		<div id="app4">
			<p>{{ site }}</p>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>

		<script>
			var vm = new Vue({
				el: "#app4",
				data: {
					name: '淘宝',
					url: "http://www.Taobao.com"
				},
				computed: {
					site: {
						// getter
						get: function() {
							return this.name + ' ' + this.url;
						},
						// setter
						set: function(newValue) {
							var names = newValue.split(' ');
							this.name = names[0];
							this.url = names[names.length - 1];
						}
					}
				}
			});
			// vm.site的值是newValue对应的实参
			vm.site = 'baidu http://www.baidu.com';
			document.write('name:' + vm.name);
			document.write('<br>');
			document.write('url:' + vm.url);
		</script>



	</body>
</html>

上面的代码,当我们执行vm.site=‘baidu http://www.baidu.com’时,数据name与url都会相对更新,视图也会更新。

2、什么是监听属性

监听属性可以针对某个属性进行监听,只要这个属性的值发生改变了,那么就会执行相应的函数

通过watch来响应数据的变化。虽然大多数情况计算属性都可以满足需要,但有时还是需要使用侦听器。当需要在数据发生变化时执行异步操作或者开销较大的操作时,就需要自定义监听器。

[实例1]:通过使用watch实现计数器:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>Title</title>
	</head>
	<body>
		<div id="app5">
			<p style="font-size: 25px;">计数器:{{ counter }}</p>
			<button @click="counter++" style="font-size: 25px"> 点击我</button>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
		<!--  -->
		<script>
			var vm1 = new Vue({
				el: "#app5",
				data: {
					counter: 1
				}
			});
			vm1.$watch('counter', function(nval, oval) {
				alert('计数器值的变化:' + oval + '变为' + nval + "!");
			})
		</script>
	</body>
</html>

注意:$watch是一个实例方法,后面是一个回调函数,这个回调函数将在counter值改变之后调用

[实例2]:千米与米之间的换算

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>Title</title>
	</head>
	<body>
		<div id="app6">
			千米:<input type="text" v-model='kilometers'>
			米:<input type="text" v-model='meters'>
			<p id="info"></p>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>>
		<script>
			var vm2 = new Vue({
				el: '#app6',
				data: {
					kilometers: 0,
					meters: 0
				},
				watch: {
					// 监听kilometers数据
					kilometers: function(val) {
						console.log(val);
						this.kilometers = val;
						this.meters = this.kilometers * 1000;
					},
					// 监听meters数据
					meters: function(val) {
						this.meters = val;
						this.kilometers = val / 1000;
					}
				}
			});
			// $watch是一个实例方法
			vm2.$watch("kilometers", function(newValue, oldValue) {
				// 这个回调函数在vm2.kilometers改变后调用
				document.getElementById('info').innerHTML = "修改前值为: " + oldValue + ",修改后值为: " + newValue;
			})
		</script>

	</body>
</html>

[computed与watch的区别]
简单来说:
1:computed是同步的,watch可以实现异步
2:computed中的函数都是带返回值的,wacth里面的函数可以不写返回值。

我们可以在watch属性的方法里执行异步操作,使用定时器来限制操作的频率吧,添加中间状态等等,这些操作都是无法用计算属性实现的。

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>Title</title>
	</head>
	<body>
		<div id="app">
			{{count}}
			<button @click="count++">点击加一</button>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
		<script>
			new Vue({
				el: "#app",
				data: {
					count: 1
				},
				watch: {
					count: function(val) {
						var that = this;
						window.setTimeout(function() {
							that.count = 0;
						}, 2000)
					}
				}
			})
		</script>
	</body>
</html>

计算属性、指令实现简单实战

实例:通过计算属性、指令等实现简单的购物车

 <style>
        table {
             border: 1px solid black;
             width: 100%;
            }
      
        th {
            height: 50px;
            }
        th, td {
            border-bottom: 1px solid #ddd;
            }
    </style>
    <div id="app7">
        <table>
            <tr>
                <th>序号</th>
                <th>商品名称</th>
                <th>购买价格</th>
                <th>购买数量</th>
                <th>操作</th>
            </tr>
            <tr v-for="iphone in IP_Json">
                <td>{{iphone.id}}</td>
                <td>{{iphone.name}}</td>
                <td>{{iphone.price}}</td>
                <td>
                    <!-- v-bind指令的参数disabled,当iPhone.count === 0时,不可再点击 -->
                    <button :disabled="iphone.count === 0" @click="iphone.count-=1">-</button>
                    {{iphone.count}}
                    <button @click="iphone.count+=1">+</button>
                </td>
                <td>
                    <button @click="iphone.count=0">移除</button>
                </td>
            </tr>
        </table>
        总价:${{totalPrice}}
    </div>
    		<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
    <script>
     var shop = new Vue({
        el:"#app7",
        data: {
            IP_Json: [{
                id: 1,
                name: "huawei",
                price: 5099,
                count: 1
              },
              {
                  id:2,
                  name: "xiaomi",
                  price: 4899,
                  count: 1
              },
              {
                  id:3,
                  name: "iphone x",
                  price: 8900,
                  count: 1
              },
            ]
        },
        computed : {
            totalPrice: function() {
                
                var totalP = 0;
                var len = this.IP_Json.length;
                for( var i= 0; i<len;i++){
                    totalP += this.IP_Json[i].price * this.IP_Json[i].count;
                }
                return totalP;
            }
        }
    })
</script>

五、第一个vue项目

入门小节

核心:数据驱动,组件化

优点:借鉴了AngularJS的模块化开发和React的虚拟Dom,虚拟Dom就是把Dmo操作放到内存中执行;

常用的属性:

v-if
v-else-if
v-else
v-for
v-on绑定事件,简写@
v-model数据双向绑定
v-bind给组件绑定参数,简写:

生命周期函数 以及vue 的计算与监听属性

说明
Vue的开发都是要基于NodeJS,实际开发采用Vue-cli脚手架开发,vue-router路由,vuex做状态管理;Vue UI,界面我们一般使用ElementUI(饿了么出品),或者ICE(阿里巴巴出品)来快速搭建前端项目~~

官网:

element ui: https://element.eleme.cn/#/zh-CN
ice: https://ice.work/

1、什么是vue-cli

vue-cli官方提供的一个脚手架,用于快速生成一个vue的项目模板;
预先定义好的目录结构及基础代码
项目的功能

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

需要的环境

  • Node.js:http://nodejs.cn/download/
    安装就是无脑的下一步就好,安装在自己的环境目录下

确认nodejs安装成功:

  • cmd下输入node -v,查看是否能够正确打印出版本号即可!
  • cmd下输入npm -v,查看是否能够正确打印出版本号即可!
    这个npm,就是一个软件包管理工具,就和linux下的apt软件安装差不多!*

安装Node.js淘宝镜像加速器(cnpm)
这样的话,下载会快很多~

# -g 就是全局安装
npm install cnpm -g

# 或使用如下语句解决npm速度慢的问题
npm install --registry=https://registry.npm.taobao.org

安装vue-cli

npm i -g @vue/cli -init
npm install -g @vue/cli
#测试是否安装成功#查看可以基于哪些模板创建vue应用程序,通常我们选择webpack
vue list
2、第一个vue-cli应用程序

1.创建一个Vue项目,我们随便建立一个空的文件夹在电脑上,我这里在D盘下新建一个目录

D:\Project\vue-study;
2.创建一个基于webpack模板的vue应用程序

#1、首先需要进入到对应的目录 cd D:\Project\vue-study
#2、这里的myvue是项目名称,可以根据自己的需求起名

vue init webpack myvue

一路都选择no即可;
说明:

Project name:项目名称,默认回车即可
Project description:项目描述,默认回车即可
Author:项目作者,默认回车即可
Install vue-router:是否安装vue-router,选择n不安装(后期需要再手动添加)
Use ESLint to lint your code:是否使用ESLint做代码检查,选择n不安装(后期需要再手动添加)
Set up unit tests:单元测试相关,选择n不安装(后期需要再手动添加)
Setupe2etests with Nightwatch:单元测试相关,选择n不安装(后期需要再手动添加)
Should we run npm install for you after the,project has been created:创建完成后直接初始化,选择n,我们手动执行;运行结果!
(1)初始化并运行

cd myvue
npm install
npm run dev
执行完成后,目录多了很多依赖

目录结构

我们通过webpack+vue-cli搭建了一个简单的vue2开发项目,走过的都知道,一个命令创建了好多文件和文件夹,一脸懵,这里,为大家简单介绍一下项目的目录结构,首先,来看整体项目目录结构的截图:
先从最外层走起(根目录文件):

  1. babelrc:作为项目babel的配置文件(Babel 是一个 JavaScript 编译器,作为我们项目对js的编译),可以将我们项目es5以上的语法编译成大多浏览器都支持的es5语法等功能,还可以在这里配置一些框架的按需加载(如element-ui)
  2. .editorconfig:项目中编辑代码风格
  3. .gitignore : 用于git的忽略文件配置(我们使用git提交的时候,有些文件是不需要提交的,如node模块的node_modules文件夹、打包成功的dist文件夹等)
  4. .postcssrc.js: 用于对style的less语法支持配置
  5. index.html: 项目的首页,即我们编写的代码会通过这个文件显示给浏览器
  6. package.json package-lock.json: 这个就不用多说了吧,大家都懂是项目包的依赖文件
  7. README.md: 展现在GitHub上的描述文件

build文件夹

  1. build.js: 作为项目打包的时候(npm run build)的入口文件,通过这个js将整体项目打包
  2. check-versions.js:用于版本node和npm版本的检测
  3. utils.js:用于项目中关于loader器的引用和项目title、icon等设置
  4. vue-loader.conf.js:因为项目是基于vue的,所以,需要一个vue-loader来识别.vue后缀的文件,这个文件,就是vue-loader的配置文件
  5. webpack.base.conf.js:项目webpack的基础配置文件
  6. webpack.dev.conf.js:开发环境下的webpack配置文件
  7. webpack.prod.conf.js:生产环境下的webpack配置文件

config文件夹

  1. dev.env.js、prod.env.js:用于配置项目的环境变量
  2. index.js:用于webpack的一些配置信息

node_modules文件夹
这个文件夹就不多说了,是项目包存储的地方

src文件夹

这个文件夹内,就是我们真正项目代码的存储地址

  1. assets文件夹:这里放置项目的模块静态资源,如css,js还有图片、字体
  2. components文件夹:使用vue的都知道,vue是模块化的框架,我们将页面中的元素分模块编写,从而提高代码修改的方便性以及重用的效率,这个文件夹内就是我们项目的模块存放地址
  3. router文件夹:vue-router让我们能处理vue的路由,从而更佳的使用component,这个文件夹内就是个vue-router的配置文件
  4. APP.vue:作为项目的根组件,也就是我们直接吧这个组件装到index.html中进行渲染
  5. main.js:作为webpack项目的入口文件,在这个文件夹内,我们是可以引用静态资源以及对整体vue的配置

static文件夹
用于存放在整体项目的静态资源,如图片,字体等

3、vue-router 路由

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

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

安装
基于第一个vue-cli进行测试学习; 先查看node modules中是否存在vue-router
vue-router是一个插件包, 所以我们还是需要用n pm/cn pm来进行安装的。打开命令行工具,进入你的项目目录,输入下面命令。

npm install vue-router@3

如果在一个模块化工程中使用它,必须要通过Vue.use()明确地安装路由功能:

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter);

测试

1、先删除没有用的东西
2、components 目录下存放我们自己编写的组件
3、定义一个Content.vue 的组件

<template>
	<div>
		<h1>内容页</h1>
	</div>
</template>

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

Main.vue组件

<template>
	<div>
		<h1>首页</h1>
	</div>
</template>

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

安装路由,在src目录下,新建一个文件夹:router,专门存放路由,配置路由index.js,如下

import Vue from'vue'
//导入路由插件
import Router from 'vue-router'
//导入上面定义的组件
import Content from '../components/Content'
import Main from '../components/Main'
//安装路由
Vue.use(Router) ;
//配置路由
export default new Router({
	routes:[
		{
			//路由路径
			path:'/content',
			//路由名称
			name:'content',
			//跳转到组件
			component:Content
			},{
			//路由路径
			path:'/main',
			//路由名称
			name:'main',
			//跳转到组件
			component:Main
			}
		]
	});

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/>'
});


App.vue中使用路由

<template>
	<div id="app">
		<!--
			router-link:默认会被渲染成一个<a>标签,to属性为指定链接
			router-view:用于渲染路由匹配到的组件
		-->
		<router-link to="/">首页</router-link>
		<router-link to="/content">内容</router-link>
		<router-view></router-view>
	</div>
</template>

<script>
	export default{
		name:'App'
	}
</script>
<style></style>

六、vue-router详解

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。路由实际上就是可以理解为指向,就是我在页面上点击一个按钮需要跳转到对应的页面,这就是路由跳转;

首先我们来学习三个单词(route,routes,router):

route:首先它是个单数,译为路由,即我们可以理解为单个路由或者某一个路由;

routes:它是个复数,表示多个的集合才能为复数;即我们可以理解为多个路由的集合,JS中表示多种不同状态的集合的形式只有数组和对象两种,事实上官方定义routes是一个数组;所以我们记住了,routes表示多个路由的集合;

router:译为路由器,上面都是路由,这个是路由器,我们可以理解为一个容器包含上述两个或者说它是一个管理者,负责管理上述两个;举个常见的场景的例子:当用户在页面上点击按钮的时候,这个时候router就会去routes中去查找route,就是说路由器会去路由集合中找对应的路由;

我们结合一个小demo来看(文章有点长,耐心慢慢看,学得慢才能进步的快,当然可以跟着一起敲):

首先需要安装vue-cli来构建一个vue的开发环境(怎么安装这里不讲,自己百度去,如果这种问题自己都解决不了的话,后面的知识可能对你来说收益不大)

安装完vue-cli之后,我们的项目目录结构如下:

img

然后我们在命令行中输入npm install vue-router --save-dev来安装vue-router,安装完之后我们可以打开package.json文件,在package.json文件中可以看到vue-router的版本号;

img

到这一步我们的准备工作就完成了,要进行写demo了;

我们在src目录下新建三个文件,分别为page1.vue和page2.vue以及router.js:

page1.vue:

<template>
    <div>
        <h1>page1</h1>
        <p>{{msg}}</p>
    </div>
</template>
<script>
    export default {
        data () {
            return {
                msg: "我是page1组件"
            }
        }
    }
</script>

page2.vue:

<template>
    <div>
        <h1>page2</h1>
        <p>{{msg}}</p>
    </div>
</template>
<script>
    export default {
        data () {
            return {
                msg: "我是page2组件"
            }
        }
    }
</script>

router.js

//引入vue
import Vue from 'vue';
//引入vue-router
import VueRouter from 'vue-router';
//第三方库需要use一下才能用
Vue.use(VueRouter)
//引用page1页面
import page1  from './page1.vue';
//引用page2页面
import page2  from './page2.vue';

//定义routes路由的集合,数组类型
const routes=[
    //单个路由均为对象类型,path代表的是路径,component代表组件
    {path:'/page1',component:page1},
    {path:"/page2",component:page2}
]

//实例化VueRouter并将routes添加进去
const router=new VueRouter({
//ES6简写,等于routes:routes
    routes
});

//抛出这个这个实例对象方便外部读取以及访问
export default router

这里我们再修改一下main.js

import Vue from 'vue'
import App from './App'
//引用router.js
import router from './router.js'
Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
//一定要注入到vue的实例对象上
  router,
  components: { App },
  template: '<App/>'
})

修改App.vue

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <div>
//router-link定义页面中点击触发部分  
      <router-link to="/page1">Page1</router-link>
      <router-link to="/page2">Page2</router-link>
    </div>
//router-view定义页面中显示部分
    <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>

就这样,我们的页面就可以进行路由跳转和切换了,路由的基本使用就完成了;但是有个问题就是我们第一次进去是看不到路由页面的,这是因为我们没有设置默认值,我们首次进入的时候路径是为空的,那么我们可以这么解决:

router.js

import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter)
import page1  from './page1.vue';
import page2  from './page2.vue';
import user   from './user.vue'

const routes=[
    {path:'/page1',component:page1},
    {path:"/page2",component:page2},
    //可以配置重定向
    {path:'',redirect:"page1"}
    //或者重新写个路径为空的路由
    {path:"",component:page1}
]

const router=new VueRouter({
    routes
});

export default router

上面的两种解决方案都是可以解决的,配置重定向的意思就是当匹配到路径为空的时候,就会重定向到page1,执行page1的路由;或者我们也可以重新配置个路由,路径为空的时候router-view展示page1的页面;

用重定向和单独配置路由的区别:

重定向实际上是当匹配到路径符合条件的时候去执行对应的路由,当然这个时候的url上面的地址显示的是对应的路由,页面也是对应的路由页面;

重新配置路由是当匹配到路径符合条件的时候,router-view页面展示部分负责拿符合条件路由的页面来展示,实际上url是没有发生变化的;

那么还有些复杂情况,是基本路由实现不了的;我们来接着往下看

动态路由匹配:

其实我们的生活中有很多这样的例子,不知道大家留意没有?比如一个网站或者后台管理系统中,在我们登录之后,是不是通常会有一个欢迎回来,XXX之类的提示语,这个我们就可以通过动态路由来实现这个效果;

首先在src目录下新建一个user.vue文件:

<template>
    <div>
        <h1>user</h1>
       //这里可以通过$route.params.name来获取路由的参数
        <p>欢迎回来,{{$route.params.name}}</p>
    </div>
</template>
<script>
    export default {
        data () {
            return {
                msg: "我是page1组件"
            }
        }
    }
</script>

然后我们修改App.vue文件的代码:

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <div>
      <router-link to="/page1">Page1</router-link>
      <router-link to="/page2">Page2</router-link>
    </div>

//添加两个router-link标签
    <div>
      <router-link to="/user/xianyu">动态路由咸鱼</router-link>
      <router-link to="/user/mengxiang">动态路由梦想</router-link>
    </div>
    <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>

修改我们的router.js

import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter)
import page1  from './page1.vue';
import page2  from './page2.vue';
import user   from './user.vue'

const routes=[
    {path:'/page1',component:page1},
    {path:"/page2",component:page2},
    // {path:'',redirect:"page1"}
    {path:"",component:page1},
 //使用冒号标记,当匹配到的时候,参数值会被设置到this.$route.params中
    {path:"/user/:name",component:user}
    
]

const router=new VueRouter({
    routes
});

export default router

配置好了,不出意外是能正常运行的,我们来看一下效果:

img

动态路由匹配给我们提供了方便,使得我们通过配置一个路由来实现页面局部修改的效果,给用户造成一种多个页面的感觉,是不是很酷!!!

酷的同时也会给我们带来一些问题,因为使用路由参数时,从/user/xianyu导航到/user/mengxiang,原来的组件实例会被复用,两个路由都渲染同个组件,比起销毁再创建,显示复用显得效率更高,带来的的只管问题就是生命周期钩子函数不会再被调用,也就是不会再被触发;但是办法总比问题多,我们可以通过监听$route对象来实现;

修改user.vue的代码

<template>
    <div>
        <h1>user</h1>
        <p>欢迎回来,{{msg}}</p>
    </div>
</template>
<script>
    export default {
        data () {
            return {
                // msg: "我是page1组件"
                msg:""
            }
        },
        watch:{
//to表示即将要进入的那个组件,from表示从哪个组件过来的
            $route(to,from){
                this.msg=to.params.name; 
                console.log(111);
            }
        }
    }
</script>

效果图如下:

img

我们可以很明显的看到我们监听的$route对象被触发了,控制台也输出了;

下面我们来一起看一下嵌套路由:

嵌套路由:

很多时候我们的页面结构决定了我们可能需要嵌套路由,比如当我们进入主页之后有分类,然后当选择其中一个分类之后进入对应的详情,这个时候我们就可以用到嵌套路由;官方文档中给我们提供了一个children属性,这个属性是一个数组类型,里面实际放着一组路由;这个时候父子关系结构就出来了,所以children属性里面的是路由相对来说是children属性外部路由的子路由;

好记性不如烂代码,让我们通过代码来看一看:

首先在我们的src目录下新建两个vue文件,分别是phone.vue和computer.vue

phone.vue

<template>
    <div>
        <p>{{msg}}</p>
    </div>
</template>
<script>
    export default {
        data () {
            return {
                msg: "嵌套手机组件"
            }
        }
    }
</script>

computer.vue

<template>
    <div>
        <p>{{msg}}</p>
    </div>
</template>
<script>
    export default {
        data () {
            return {
                msg: "嵌套电脑组件"
            }
        }
    }
</script>

然后我们再修改我们的App.vue文件:

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <div>
      <router-link to="/page1">Page1</router-link>
    </div>
    <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>

通过上面的App.vue文件我们可以看到,我们此时页面只有一个page1的标签了;

我们再来修改router.js

import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter)
const originalPush = Router.prototype.push
Router.prototype.push = function push(location) {
    return originalPush.call(this, location).catch(err => err)
}
import page1  from './page1.vue';
import page2  from './page2.vue';
import user   from './user.vue';
import phone  from './phone.vue';
import computer from './computer.vue'

const routes=[
    {
        path:'/page1',
        component:page1,
        children: [
            {
                path: "phone",
                component: phone
            },
            {
                path: "computer",
                component: computer
            },
        ]
    },
    // {path:"/page2",component:page2},
    // // {path:'',redirect:"page1"}
    // {path:"",component:page1},
    // {path:"/user/:name",component:user}
    
]

const router=new VueRouter({
    routes
});

export default router

为了大家看的直观点,其他路由全部注释了,页面只剩下/page1这一个路由了;

img

上面说到了,children属性其实就是一个子路由集合,数组结构里面放着子路由;

效果图如下:

img

vue中this.$router.push()路由传值和获取的两种常见方法

1、路由传值 this.$router.push()

(1) 想要导航到不同的URL,使用router.push()方法,这个方法会向history栈添加一个新纪录,所以,当用户点击浏览器后退按钮时,会回到之前的URL

(2)当点击 时,这个方法会在内部调用,即点击 等同于调用 router.push(…)

a) 声明式:

b) 编程式:router.push(…)

c) 该方法的参数可以是一个字符串路径,或者一个描述地址的对象

[复制代码](javascript:void(0)😉

// 字符串
router.push('home')
 
// 对象
this.$router.push({path: '/login?url=' + this.$route.path});
 
// 命名的路由
router.push({ name: 'user', params: { userId: 123 }})
 
// 带查询参数,变成/backend/order?selected=2
this.$router.push({path: '/backend/order', query: {selected: "2"}});
 
// 设置查询参数
this.$http.post('v1/user/select-stage', {stage: stage})
      .then(({data: {code, content}}) => {
            if (code === 0) {
                // 对象
                this.$router.push({path: '/home'});
            }else if(code === 10){
                // 带查询参数,变成/login?stage=stage
                this.$router.push({path: '/login', query:{stage: stage}});
           }
});
 
// 设计查询参数对象
let queryData = {};
if (this.$route.query.stage) {
    queryData.stage = this.$route.query.stage;
}
if (this.$route.query.url) {
    queryData.url = this.$route.query.url;
}
this.$router.push({path: '/my/profile', query: queryData});

[复制代码](javascript:void(0)😉

img

2、获取参数的两种常用方法:params和query

(1)由于动态路由也是传递params的,所以在 this.$router.push() 方法中path不能和params一起使用,否则params将无效。需要用name来指定页面。

及通过路由配置的name属性访问

imgimg

this.$router.push({name:"menuLink",params:{alert:"页面跳转成功"}})

(2)在目标页面通过this.$route.params获取参数:

<p>提示:{{this.$route.params.alert}}</p>

(3)在目标页面通过this.$route.query 获取参数

//传值
this.$router.push({path:"/menLink",query:{alert:"页面跳转成功"}})

//用query获取值
<p>提示:{{this.$route.query.alert}}</p>

两种方式的区别是query传参的参数会带在url后边展示在地址栏,params传参的参数不会展示到地址栏。需要注意的是接收参数的时候是route而不是router。两种方式一一对应,名字不能混用

vue-router路由守卫

在项目开发中每一次路由的切换或者页面的刷新都需要判断用户是否已经登录,前端可以判断,后端也会进行判断的,我们前端最好也进行判断。

vue-router提供了导航钩子:全局前置导航钩子 beforeEach和全局后置导航钩子 afterEach,他们会在路由即将改变前和改变后进行触发。所以判断用户是否登录需要在beforeEach导航钩子中进行判断。

导航钩子有3个参数:

1、to:即将要进入的目标路由对象;

2、from:当前导航即将要离开的路由对象;

3、next :调用该方法后,才能进入下一个钩子函数(afterEach)。

​ next()//直接进to 所指路由
​ next(false) //中断当前路由
​ next(‘route’) //跳转指定路由
​ next(‘error’) //跳转错误路由

beforeEach:

路由配置文件:

import  Vue from 'vue'
import  Router from 'vue-router'
import   HelloWorld from '@/components/HelloWorld'
import  HomePage from '@/pages/home.vue'
Vue.use(Router)
const router=new Router({
 routes: [
  {
  path: '/',
  name: 'HelloWorld',
   component: HelloWorld
  },
   {
   path: '/home',
   name: 'home',
   component: HomePage
 },
   {
   path:'*',
   redirect:'/home'
   }
],
})
 ``router.beforeEach((to,from,next)=>{
  ``console.log(to);
  ``console.log(from);
  ``next();
 ``})
 ``export` `default` `router;

打印结果如下

img

实现用户验证的代码:

router.beforeEach((to, from, next) => {
    //我在这里模仿了一个获取用户信息的方法
  let isLogin = sessionStorage.getItem('userInfo');
    if (isLogin) {
        //如果用户信息存在则往下执行。
        next()
    } else {
        //如果用户token不存在则跳转到login页面
        if (to.path === '/login') {
            next()
        } else {
            next('/login')
        }
    }
})

afterEach:

和beforeEach不同的是afterEach不接收第三个参数 next 函数,也不会改变导航本身,一般beforeEach用的最多,afterEach用的少.

router.afterEach((to,from)=>{ //这里不接收next
    console.log(to);
    console.log(from);
})

组件内路由守卫

  // 跟methods: {}等同级别书写,组件路由守卫是写在每个单独的vue文件里面的路由守卫
 	  1.beforeRouteEnter (to, from, next) {
 	        // 注意,在路由进入之前,组件实例还未渲染,所以无法获取this实例,只能通过vm来访问组件实例
 	        next(vm => {})
 	    }
 	   2.beforeRouteUpdate (to, from, next) {
 	        // 同一页面,刷新不同数据时调用,
 	    }
 	   3.beforeRouteLeave (to, from, next) {
 	        // 离开当前路由页面时调用
 	    }

router独享守卫

路由独享守卫是在路由配置页面单独给路由配置的一个守卫

 export default new VueRouter({
     routes: [
         {
             path: '/',
             name: 'home',
             component: 'Home',
             beforeEnter: (to, from, next) => {
                // ...
             }
         }
     ]
 })

注意:重复点击同一路由会报错

const originalReplace = Router.prototype.replace;
Router.prototype.replace = function replace(location) {
    return originalReplace.call(this, location).catch(err => err);
};
const originalPush = Router.prototype.push
Router.prototype.push = function push(location) {
    return originalPush.call(this, location).catch(err => err)
}

七、vue组件化

vuecli 中提供一种简单的方式进行组件化开发。

我们来分析一下下面代码的嵌套逻辑,假如我们将所有的代码逻辑都放到一个App.vue 组件中:

image-20210807201258559

我们会发现,将所有的代码逻辑全部放到一个组件中,代码是非常的臃肿和难以维 护的。
并且在真实开发中,我们会有更多的内容和代码逻辑,对于扩展性和可维护性来说 都是非常差的。
所以,在真实的开发中,我们会对组件进行拆分,拆分成一个个功能的小组件。

组件的拆分

我们可以按照如下的方式进行拆分:

img

定义组件
  • 组件可以使用ES6模块化规范的写法,通过 import 引入其他vue或者js模块。
  • data 部分必须是方法而不是json对象!
<template>
  <div>
    {{msg}}
  </div>
</template>

<script>
export default {
  // 其他组件
  components: {},
  // 生命周期
  created() {},
  mounted() {},
  // vue 变量
  data() {
    return {
      msg: 'hello world',
    }
  },
  // 方法
  methods: {},
  // 来自父组件的消息
  props: {},
}
</script>

按照如上的拆分方式后,我们开发对应的逻辑只需要去对应的组件编写就可。

App.vue文件

<template lang="html">
  <!-- 使用局部组件 -->
  <div id="app">
    <Header></Header>
    <!-- 如果组件不需要填充数据或标签等,可以直接使用单标签 -->
    <Main/>
    <Footer/>
  </div>
</template>
<script>
// 导入vue组件
import Header from "./Header";
import Main from "./Main";
import Footer from "./Footer";
export default {
  data() {
    return {};
  },
  // 注册局部组件
  components: {
    Header,
    Main,
    Footer,
  },
};
</script>
<style scoped>
</style>

Header.vue

<template lang="">
  <div id="header">
    <h2>哈哈哈</h2>
    <h2>header</h2>
  </div>
</template>
<script>
export default {}
</script>
<style scoped>
</style>

Main.vue


<template>
  <div id="main">
    <h2>haha</h2>
    <h3>main</h3>
    <main-banner></main-banner>
  </div>
</template>

<script>
import MainBanner from "./MainBanner.vue";
export default {
  components: { MainBanner },
};
</script>

<style scoped>
</style>

MainBanner.vue

<template>
  <ul>
      <li>商品信息1</li>
      <li>商品信息2</li>
      <li>商品信息3</li>
      <li>商品信息4</li>
      <li>商品信息5</li>
    </ul>
</template>

<script>
  export default {
    
  }
</script>

<style scoped>

</style>

Footer.vue


<template>
  <div id="footer">
    <h2>main</h2>
    <h2>hahah</h2>
  </div>
</template>

<script>
export default {

}
</script>

<style scoped>
</style>

组件的通信

上面的嵌套逻辑如下,它们存在如下关系:

App组件是Header、Main、Footer组件的父组件;
Main组件是Banner、ProductList组件的父组件;
在开发过程中,我们会经常遇到需要组件之间相互进行通信:

比如App可能使用了多个Header,每个地方的Header展示的内容不同,那么我们就需要使用者传递给Header 一些数据,让其进行展示;
又比如我们在Main中一次性请求了Banner数据和ProductList数据,那么就需要传递给它们来进行展示;
也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件;
总之,在一个Vue项目中,组件之间的通信是非常重要的环节,所以接下来我们就具体学习一下组件之间是如何相 互之间传递数据的;

父子组件之间通信的方式
父子组件之间如何进行通信呢?

父组件传递给子组件:通过props属性;
子组件传递给父组件:通过$emit触发事件;

image-20210807201629967

父组件传递给子组件
在开发中很常见的就是父子组件之间通信,比如父组件有一些数据,需要子组件来进行展示:

这个时候我们可以通过props来完成组件之间的通信;

什么是Props呢?

Props是你可以在组件上注册一些自定义的attribute (属性);
父组件给这些attribute赋值,子组件通过attribute的名称获取到对应的值;
Props有两种常见的用法:

方式一:字符串数组,数组中的字符串就是attribute的名称;
方式二:对象类型,对象类型我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、 默认值等等
Props的数组用法

子组件接收

img

父组件发送

img

Props的对象用法
数组用法中我们只能说明传入的attribute的名称,并不能对其进行任何形式的限制,接下来我们来看一下对象的 写法是如何让我们的props变得更加完善的。

当使用对象语法的时候,我们可以对传入的内容限制更多:

比如 指定传入的attribute的类型;
比如 指定传入的attribute是否是必传的;
比如 指定没有传入时,attribute的默认值;

img

细节一:type类型

那么type的类型都可以是哪些呢?

  1. String
  2. Number
  3. Boolean
  4. Array
  5. Object
  6. Date
  7. Function
  8. Symbol(ES6新增的基本类型)

细节二:对象类型的其他写法

img

img
子组件传递给父组件
什么情况下子组件需要传递内容到父组件呢

当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容;
子组件有一些内容想要传递给父组件的时候;
我们如何完成上面的操作呢?

首先,我们需要在子组件中定义好在某些情况下触发的事件名称;
其次,在父组件中以v-on的方式传入要监听的事件名称,并且绑定到对应的方法中;
最后,在子组件中发生某个事件的时候,根据事件名称触发对应的事件;

自定义事件的流程

我们封装一个CounterOperation.vue的组件:

内部其实是监听两个按钮的点击,点击之后通过 this.$emit的方式发出去事件;

子组件语法格式

img

父级组件接收方法:

img

自定义事件的参数和验证

自定义事件的时候,我们也可以传递一些参数给父组件:

子组件

img

父组件

img

img

事件总线(EventBus)

父子组件通讯原则

为了提高组件的独立性与重用性,父组件会通过 props 向下传数据给子组件,当子组件有事情要告诉父组件时会通过 $emit 事件告诉父组件。如此确保每个组件都是独立在相对隔离的环境中运行,可以大幅提高组件的维护性。

img

那兄弟组件可以用什么?那就是Vue中的 事件总线 ,即 **EventBus **。

EventBus的简介

EventBus 又称为事件总线。在Vue中可以使用 EventBus 来作为沟通桥梁的概念,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件,但也就是太方便所以若使用不慎,就会造成难以维护的灾难,因此才需要更完善的Vuex作为状态管理中心,将通知的概念上升到共享状态层次。

如何使用EventBus

问题背景:组件传值;在项目开发中,会发现组件传值是一个组基本的操作,也是用的最多的。但是很多时候可能涉及到爷爷和孙子,甚至重孙子之间需要的传值。这个时候eventBus就到了大显身手的时候了。
eventBus 嗯 ,就叫一个事件公共汽车吧。每个人把需要共享给别人的物品就放在这个车上,谁需要了就可以去拿,这样子是不是很方便,每个人都可以访问到,每个人也可以往这个车子上放东西。
下面开始这个过程,

初始化

首先你需要做的是创建事件总线并将其导出,以便其它模块可以使用或者监听它。我们可以通过两种方式来处理。先来看第一种,新创建一个 .js 文件,比如 event-bus.js

// event-bus.js
 
 
import Vue from 'vue'
export const EventBus = new Vue()

你需要做的只是引入 Vue 并导出它的一个实例(在这种情况下,我称它为 EventBus )。实质上它是一个不具备 DOM 的组件,它具有的仅仅只是它实例方法而已,因此它非常的轻便。

另外一种方式,可以直接在项目中的 main.js 初始化 EventBus

// main.js
Vue.prototype.$EventBus = new Vue()// main.js

注意,这种方式初始化的 EventBus 是一个 全局的事件总线

现在我们已经创建了 EventBus ,接下来你需要做到的就是在你的组件中加载它,并且调用同一个方法,就如你在父子组件中互相传递消息一样。

发送事件
假设你有两个子组件: DecreaseCount 和 IncrementCount ,分别在按钮中绑定了 decrease()和 increment() 方法。这两个方法做的事情很简单,就是数值递减(增) 1 ,以及角度值递减(增) 180 。在这两个方法中,通过 EventBus.$emit(channel: string, callback(payload1,…)) 监听 decreased (和 incremented )频道。

<!-- DecreaseCount.vue -->
<template>
    <button @click="decrease()">-</button>
</template>
 
<script> import { EventBus } from "../event-bus.js";
    export default {
        name: "DecreaseCount",
        data() {
            return {
                num: 1,
                deg:180
            };
        },
        methods: {
            decrease() {
                EventBus.$emit("decreased", {
                    num:this.num,
                    deg:this.deg
                });
            }
        }
    }; 
</script>
 
<!-- IncrementCount.vue -->
<template>
    <button @click="increment()">+</button>
</template>
 
<script> import { EventBus } from "../event-bus.js";
    export default {
        name: "IncrementCount",
        data() {
            return {
                num: 1,
                deg:180
            };
        },
        methods: {
            increment() {
                EventBus.$emit("incremented", {
                    num:this.num,
                    deg:this.deg
                });
            }
        }
    };
 </script>

上面的示例,在 DecreaseCount 和 IncrementCount 分别发送出了 decreased 和 incremented频道。接下来,我们需要在另一个组件中接收这两个事件,保持数据在各组件之间的通讯。

接收事件
现在我们可以在组件 App.vue 中使用 EventBus.$on(channel: string, callback(payload1,…))监听 DecreaseCount 和 IncrementCount 分别发送出了 decreased 和 incremented 频道。

<!-- App.vue -->
<template>
    <div id="app">
        <div class="container" :style="{transform: 'rotateY(' + degValue + 'deg)'}">
            <div class="front">
                <div class="increment">
                    <IncrementCount />
                </div>
                <div class="show-front"> {{fontCount}} </div>
                <div class="decrement">
                    <DecreaseCount />
                </div>
            </div>
 
            <div class="back">
                <div class="increment">
                    <IncrementCount />
                </div>
                <div class="show-back"> {{backCount}} </div>
                <div class="decrement">
                    <DecreaseCount />
                </div>
            </div> 
        </div>
    </div>
</template>
 
<script>
    import IncrementCount from "./components/IncrementCount";
    import DecreaseCount from "./components/DecreaseCount";
    import { EventBus } from "./event-bus.js";
    export default {
        name: "App",
        components: {
            IncrementCount,
            DecreaseCount
        },
        data() {
            return {
                degValue:0,
                fontCount:0,
                backCount:0
            };
        },
        mounted() {
            EventBus.$on("incremented", ({num,deg}) => {
                this.fontCount += num
                this.$nextTick(()=>{
                    this.backCount += num
                    this.degValue += deg;
                })
            });
            EventBus.$on("decreased", ({num,deg}) => {
                this.fontCount -= num
                this.$nextTick(()=>{
                    this.backCount -= num
                    this.degValue -= deg;
                })
            });
        }
    }; 
</script>

最终得到的效果如下:

最后用一张图来描述示例中用到的 EventBus 之间的关系:

img

如果你只想监听一次事件的发生,可以使用 EventBus.$once(channel: string, callback(payload1,…)) 。

移除事件监听者
如果想移除事件的监听,可以像下面这样操作:

import { eventBus } from './event-bus.js'
EventBus.$off('decreased', {})

你也可以使用 EventBus. o f f ( ‘ d e c r e a s e d ’ ) 来移除应用内所有对此事件的监听。或者直接调用 E v e n t B u s . off(‘decreased’) 来移除应用内所有对此事件的监听。或者直接调用EventBus. off(decreased)来移除应用内所有对此事件的监听。或者直接调用EventBus.off() 来移除所有事件频道, 注意不需要添加任何参数 。

上面就是 EventBus 的使用方式,是不是很简单。上面的示例中我们也看到了,每次使用 EventBus 时都需要在各组件中引入 event-bus.js 。事实上,我们还可以通过别的方式,让事情变得简单一些。那就是创建一个全局的 EventBus 。接下来的示例向大家演示如何在Vue项目中创建一个全局的 EventBus 。

全局EventBus

全局EventBus,虽然在某些示例中不提倡使用,但它是一种非常漂亮且简单的方法,可以跨组件之间共享数据。

它的工作原理是发布/订阅方法,通常称为 Pub/Sub

  • 有一个全局EventBus
  • 所有事件都订阅它
  • 所有组件也发布到它,订阅组件获得更新
  • 总结一下。所有组件都能够将事件发布到总线,然后总线由另一个组件订阅,然后订阅它的组件将得到更新

在代码中,我们将保持它非常小巧和简洁。我们将它分为两部分,将展示两个组件以及生成事件总线的代码。

var EventBus = new Vue();
 
Object.defineProperties(Vue.prototype, {
    $bus: {
        get: function () {
            return EventBus
        }
    }
})

现在,这个特定的总线使用两个方法 $on 和 e m i t 。一个用于创建发出的事件,它就是 emit 。一个用于创建发出的事件,它就是 emit。一个用于创建发出的事件,它就是emit ;另一个用于订阅 $on :

var EventBus = new Vue();
 
this.$bus.$emit('nameOfEvent',{ ... pass some event data ...});
 
this.$bus.$on('nameOfEvent',($event) => {
    // ...
})

现在,我们创建两个简单的组件,以便最终得出结论。

接下来的这个示例中,我们创建了一个 ShowMessage 的组件用来显示信息,另外创建一个 UpdateMessage 的组件,用来更新信息。

在 UpdateMessage 组件中触发需要的事件。在这个示例中,将触发一个 updateMessage 事件,这个事件发送了 updateMessage 的频道:

<!-- UpdateMessage.vue -->
<template>
    <div class="form">
        <div class="form-control">
            <input v-model="message" >
            <button @click="updateMessage()">更新消息</button>
        </div>
    </div>
</template>
<script>
export default {
        name: "UpdateMessage",
        data() {
            return {
                message: "这是一条消息"
            };
        },
        methods: {
            updateMessage() {
                this.$bus.$emit("updateMessage", this.message);
            }
        },
        beforeDestroy () {
            this.$bus.$off('updateMessage')
        }
    };
 </script>

同时在 ShowMessage 组件中监听该事件:

<!-- ShowMessage.vue -->
<template>
    <div class="message">
        <h1>{{ message }}</h1>
    </div>
</template>
 
<script> 
export default {
        name: "ShowMessage",
        data() {
            return {
                message: "我是一条消息"
            };
        },
        created() {
            var self = this
            this.$bus.$on('updateMessage', function(value) {
                self.updateMessage(value);
            })
        },
        methods: {
            updateMessage(value) {
                this.message = value
            }
        }
    }; 
</script><

最终的效果如下:

从上面的代码中,我们可以看到 ShowMessage 组件侦听一个名为 updateMessage 的特定事件,这个事件在组件实例化时被触发,或者你可以在创建组件时触发。另一方面,我们有另一个组件UpdateMessage ,它有一个按钮,当有人点击它时会发出一个事件。这导致订阅组件侦听发出的事件。这产生了 Pub/Sub 模型,该模型在兄弟姐妹之间持续存在并且非常容易实现。

八、全局状态管理组件vuex

vuex:https://vuex.vuejs.org/zh/

1.vuex简介

Vuex 是一个专为 Vue 开发的应用程序的状态管理模式,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。( vuex 是全局状态管理组件 )

简而言之,Vuex 采用类似全局对象的形式来管理所有组件的公用数据,如果想修改这个全局对象的数据,得按照Vuex提供的方式来修改(不能自己随意用自己的方式来修改)。

2.vuex优点

Vuex状态管理跟使用传统全局变量的不同之处:

  1. Vuex的状态存储是响应式的: 就是当你的组件使用到了这个 Vuex 的状态,一旦它改变了,所有关联的组件都会自动更新相对应的数据,这样开发者省事很多。

  2. 不能直接修改Vuex的状态: 如果是个全局对象变量,要修改很容易,但是在 Vuex 中不能这样做,想修改就得使用 Vuex 提供的唯一途径:显示地提交(commit)mutations来实现修改。这样做的好处就是方便我们跟踪每一个状态的变化,在开发过程中调试的时候,非常实用

3.什么情况下我应该使用 Vuex?

Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。

如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式 (opens new window)就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。引用 Redux 的作者 Dan Abramov 的话说就是:

Flux 架构就像眼镜:您自会知道什么时候需要它。

4.使用步骤

1.安装

npm install vuex@3 --save

2. 引用Vuex

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

3. 创建仓库Store

要使用 Vuex,我们要创建一个实例 store,我们称之为仓库,利用这个仓库 store 来对我们的状态进行管理。

 //创建一个 store
 const store = new Vuex.Store({});

4、包含模块

  • State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
  • Getter:允许组件从 store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
  • Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
  • Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
  • Module:可以将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块

Vuex的作用类似全局对象,Vuex 使用单一状态树,用一个对象State包含了整个应用层级的所有状态,你可以理解为这些状态就是一堆全局变量和数据。

img

1. State

假设我们有一个全局状态 count 的值为 5。那么,我们就可以将其定义为 state 对象中的 keyvalue,作为全局状态供我们使用。如下:

 //创建一个 store
 const store = new Vuex.Store({
    //state存储应用层的状态
    state:{
        count:5  //总数:5
    }
 });

2.Getters

可以认为,getters 是store的计算属性,类似于computed,对state里的数据进行一些过滤,改造等等

假设我们要在state.count的基础上派生出一个新的状态newCount出来,就适合使用我们的 getters

getters 接受 state 作为其第一个参数

const store = new Vuex.Store({
   //state存储应用层的状态
   state:{
      count:5  //总数:5
   },
   getters:{
      newCount:state => state.count * 3
   }
});

在组件获取newCount的方式

export default {
  computed: {
      newCount(){
          return this.$store.getters.newCount;
      }
  }
};  

3. Mutations

Vuex 给我们提供修改仓库 store中的状态的唯一办法就是通过提交mutation ,且必须是同步函数

我们在 mutations中定义了一个叫increment的函数,函数体就是我们要进行更改的地方

会接受 state作为第一个参数,第二个是自定义传参

 const store = new Vuex.Store({
    //state存储应用层的状态
    state:{
        count:5  //总数:5
    },
    // mutations是修改state中数据的唯一途径
    mutations:{
        increment(state,value){
            state.count += value;
        }
    }
 });

我们在提交commit时候,第一个参数"increment",就是对应在 mutations中的increment方法,第二个参数是自定义值。例如:

 methods: {
   getVal(event) {
     //获取当前的按键的值
     let value = event.target.dataset.value;
     //通过commit提交一个名为increment的mutation
     this.$store.commit("increment", value);
   }
 }

在组建中获取count的方法

export default {
  computed: {
      count(){
          return this.$store.state.count;
      }
  }
};  

4. Action
  1. 用于提交 mutation,而不是直接变更状态,可以包含任意异步操作
  2. 只有通过 action=>mutations=>states ,这个流程进行操作,具体步骤如下:
export default new Vuex.Store({
    //存放数据
    state: {
        obj: {},
    },
    //4. 通过commit mutations中的方法来处理
    mutations: {
        getParam(state, Object) {
            //5.修改state中的数据
            state.obj = Object
        }
    },
    //2.接受dispatch传递过来的方法和参数
    actions: {
        getParamSync(store, Object) {
            // 处理异步操作
            setTimeout(() => {
                //3.通过commit提交一个名为getParam的mutation
                //action 函数接收一个 store 的实例对象,因此你可以调用 store.commit 提交一个 mutation
                store.commit('getParam', Object);
            }, 1000)
        }
    }
})

在组件里面我们怎么使用

methods: {
   getVal() {
	  let name= 'xia';
	  let age= '26';
	  let sex= 'man';
	  //1.通过dispatch将方法getParamSync和多个参数{name,age,sex}传递给actions
	  this.$store.dispatch('getParamSync',{name,age,sex})
   }
}

5. Modules

随着项目的复杂度增大,为了方便管理 Vuex,一般会将其按功能分割成不同的模块(Module),方便日后管理。每个模块拥有自己的 statemutationactiongetter 甚至是嵌套子模块

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import * as getters from './getters'

import moduleA from './module/moduleA' // 模块A
import moduleB from './module/moduleB' // 模块B

Vue.use(Vuex)

export default new Vuex.Store({
    actions,
    getters,
    state,
    mutations,
    modules: {
        moduleA,
        moduleB
    }
})

moduleA.js / moduleB.js 文件

// 每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块
export default {
    state: {
        text: 'moduleA'
    },
    getters: {},
    mutations: {},
    actions: {}
}

然后我们在组件中可以这样调取

<template>
	<div class="demo">
		<h1>{{getText1}}</h1>
		<h1>{{getText2}}</h1>
	</div>
</template>

computed: {
    getText1(){
    	return this.$store.state.moduleA.text;
    },
    //或
	...mapState({
		getText2: state => state.moduleB.text;
	})
}

由此可知,模块内部的 state 是局部的,只属于模块本身所有,所以外部必须通过对应的模块名进行访问。

5.Vuex的四个辅助函数:
  • 语法糖,四大金刚辅助函数:mapState,mapActions,mapMutations,mapGetters
import { mapState, mapGetters, mapActions, mapMutations } from "vuex";
export default {
  computed: {
    ...mapGetters([
      "": "vuex仓库定义的方法",
   ]),
    ...mapState({}),
  },
  methods: {
    ...mapMutations({}),
    ...mapActions({}),
  },
};
</script>

具体使用后

computed: {
    ...mapState({
      count: (state) => {
        return state.count;
      },
    }),
    ...mapGetters(['newcount']
    )
  },
   methods: {
      ...mapMutations(['increment']),
    send(e) {
      this.$router.push(e.value);
    },
    muchange() {
        this.increment(10)
    },
  },
  
  使用异步
   methods: {
      ...mapMutations(['increment']),
      ...mapActions(['add']),
    send(e) {
      this.$router.push(e.value);
    },
    muchange() {
       this.add(10)
    },
  },

九、 axios请求详解

1.axios定义

axios是什么:axios它是基于promise的http库,可运行在浏览器端和node.js中,然后作者尤雨溪也是果断放弃了对其官方库vue-resource的维护,直接推荐axios库

axios的作用:axios主要是用于向后台发起请求的,还有在请求中做更多是可控功能。

具备以下特征:

 a.从浏览器中创建 XMLHttpRequest
 b.从 node.js 发出 http 请求
 c.支持 Promise API
 e.拦截请求和响应
 f.转换请求和响应数据
 g.取消请求
 h.自动转换JSON数据
 i.客户端支持防止 CSRF/XSRF

Promise是什么:Promise 是异步编程的一种解决方案,比传统的解决方案–回调函数和事件--更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了语法,原生提供了Promise。

是一个对象用来传递异步操作的信息,它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的api,可供进一步的处理。

promise的作用:Promise的出现主要是解决地狱回调的问题,比如你需要结果需要请求很多个接口,这些接口的参数需要另外那个的接口返回的数据作为依赖,这样就需要我们一层嵌套一层,但是有了Promise 我们就无需嵌套。

promise的本质是什么:分离异步数据获取和业务

1、 安装axios:

npm install axios

2、 在App.vue中引入axios:

import axios from 'axios'

Vue.use(axios) // 注意 这样的用法是错误的,axios不支持Vue.use()的声明方式

但如果将 axios 改写为 Vue 的原型属性,就能解决这个问题

Vue.prototype.$ajax = axios

在 main.js 中添加了这两行代码之后,就能直接在组件的 methods 中使用 $ajax 命令;

如果不加 Vue.prototype.$ajax = axios这句的话,则直接使用axios.get()

3.请求配置

axios中的请求配置参数包括以下内容:

{
  // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。一般用于全局路径配置
  // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
  baseURL: 'https://some-domain.com/api/',

  // `url` 是用于请求的服务器 URL
  url: '/user',

  // `method` 是创建请求时使用的方法
  method: 'get', // 默认是 get

  // `params` 是即将与请求一起发送的 URL 参数 请求方式是get 就需要用params
  // 必须是一个无格式对象(plain object)或 URLSearchParams 对象
  params: {
    ID: 12345
  },

  // `data` 是作为请求主体被发送的数据
  // 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - 浏览器专属:FormData, File, Blob
  data: {
    firstName: 'Fred'
  },

  // `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
  // 如果请求话费了超过 `timeout` 的时间,请求将被中断
  timeout: 10000,
  // `headers` 是即将被发送的自定义请求头
  headers: {'X-Requested-With': 'XMLHttpRequest'},

  // `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
  responseType: 'json', // 默认的
}

配置的优先顺序

配置会以一个优先顺序进行合并。这个顺序是:在 lib/defaults.js 找到的库的默认值,然后是实例的 defaults 属性,最后是请求的 config 参数。后者将优先于前者。这里是一个例子:

// 使用由库提供的配置的默认值来创建实例
// 此时超时配置的默认值是 `0`
var instance = axios.create();

// 覆写库的超时默认值
// 现在,在超时前,所有请求都会等待 2.5 秒
instance.defaults.timeout = 2500;

// 为已知需要花费很长时间的请求覆写超时设置
instance.get('/longRequest', {
  timeout: 5000
});

响应结构

响应返回内容结构如下:

{
  // `data` 由服务器提供的响应
  data: {},

  // `status` 来自服务器响应的 HTTP 状态码
  status: 200,

  // `statusText` 来自服务器响应的 HTTP 状态信息
  statusText: 'OK',

  // `headers` 服务器响应的头
  headers: {},

  // `config` 是为请求提供的配置信息
  config: {}
}

2.axios 简单使用

使用get请求数据

// 向具有指定ID的用户发出请求
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
 
// 也可以通过 params 对象传递参数
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

执行 POST 请求

axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
3.拦截器

在请求或响应被 then 或 catch 处理前拦截它们。

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
  });

4.实战vue项目封装axios

基本配置如下所示:

import axios from 'axios'
// 创建axios实例
const service = axios.create({
  baseURL: ‘https://localhost:3000', // api的base_url
  timeout: 35000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(
config => {
	//处理内容根据自己的业务需求,这里以上传token为例
  if (store.getters.token) {
    let test = config.data;
    if(test){
      config.data['access_token']= getToken()
    }

    if (get('storeId') && 'undefined'!=get('storeId')){
      config.headers['storeid'] = get('storeId')
    }
    config.headers['Authorization'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
  }else{
    config.headers['client_id'] = 'app';
    config.headers['client_secret'] = 'app';
  }
  return config
}, error => {
  // Do something with request error
  console.log(error) // for debug
  Promise.reject(error)
})

// respone拦截器
service.interceptors.response.use(
  response => {
  /**
  * code为非200是抛错 可结合自己业务进行修改
  */
    const res = response.data
    if (res.code !== 200 && !res.access_token) {
      console.log(res);
      Message({
        message: res.msg,
        type: 'error',
        duration: 3 * 1000
      })

      // 401:未登录;
      if (res.code === 401||res.code === 403) {
        MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
          confirmButtonText: '重新登录',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          store.dispatch('FedLogOut').then(() => {
            location.reload()// 为了重新实例化vue-router对象 避免bug
          })
        })
      }
      return Promise.reject('error')
    } else {
      return response.data
    }
  },
  error => {
    console.log(error);
    Message({
      message: error.message,
      type: 'error',
      duration: 3 * 1000
    })
    return Promise.reject(error)
  }
)

export default service


封装完成之后,只需要在对应需要调用http请求的地方,引入调用即可,如下所示:

//上述封装的文件名是request.js
import request from '/request'

/**
 * @param {Object} username
 * @param {Object} password
 * 登录
 */
export function login(username,password){
  return request({
    url: '/sys/sysUser/login',
    method: post,
    data: {
      username,
      password
    }
  })
}

十. vue2补充

$attrs

关于$attrs, vue官网如是介绍:

包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs"传入内部组件——在创建高级别的组件时非常有用。

a t t r s 大白话介绍 a t t r s 就是属性的意思即 a t t r i b u t e , j s 的 s e t A t t r i b u t e , g e t A t t r i b u t e 听过把, j q 的 attrs大白话介绍 attrs就是属性的意思即attribute, js的setAttribute, getAttribute听过把,jq的 attrs大白话介绍attrs就是属性的意思即attributejssetAttribute,getAttribute听过把,jq().attr有用过吧,他们是用来设置啥的?不就是设置类似于title, data-x, src这类的属性么,由此延伸,大概可知道vue实例里的this.$attrs是啥了。
再来,如有一个父组件是这样的:

<father :age="age" :sex="sex" title="ohNo" data-height="100"></father>

如上,很明显age, sex在子组件中可通过props来接受这些值,这就完成可父组件向子组件传值,可注意,这时候props可拿不到像title与data-height的值,这时候在子组件打印this.$attrs,你会发现是这样子的:

这样子就可以拿到这些属性值啦,值得注意的是,class跟style不属于属性值。

a t r r s 深传递如果有这样一种情况呢,子组件可以通过 t h i s . atrrs深传递 如果有这样一种情况呢,子组件可以通过this. atrrs深传递如果有这样一种情况呢,子组件可以通过this.attrs的拿到父组件的属性值,然后孙组件呢,如果在子组件上面没有定义属性,在孙组件里打印this. a t t r s 其实是个控制,为啥?因为子组件没定义属性啊,相要在孙组件乃至更深层的后代里拿到父组件的属性值,可以在相应子组件里通过 v − b i n d = " attrs其实是个控制,为啥?因为子组件没定义属性啊,相要在孙组件乃至更深层的后代里拿到父组件的属性值,可以在相应子组件里通过v-bind=" attrs其实是个控制,为啥?因为子组件没定义属性啊,相要在孙组件乃至更深层的后代里拿到父组件的属性值,可以在相应子组件里通过vbind="attrs"即可传递父组件的属性值至下一代子组件,若要继续往下传递,相应字组件也要添加v-bind=“$attrs”
如:
父组件:

<father :age="age" :sex="sex" title="ohNo" data-height="100"></father>

子组件:

<child v-bind="$attrs"></child>

这里this.$attrs是{data-height: “45”, title: “ohNo”}

孙组件

 <boy  v-bind="attrs"></boy>

这里this. a t t r s 是 d a t a − h e i g h t : " 45 " , t i t l e : " o h N o " ,如果子组件没有加 v − b i n d = " attrs是{data-height: "45", title: "ohNo"},如果子组件没有加v-bind=" attrsdataheight:"45",title:"ohNo",如果子组件没有加vbind="attrs",这里打印this.$attrs为空对象

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~Wforikl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值