各位同学,这是根据B站张天禹老师的教学视频,以一个后端java开发人员的角度学习的个人总结的笔记,Vue2+vue3,并且对于一些抽象概念百度理解后的个人解释,Vue2笔记总共36524字(已经更新到53954字),Vue3笔记13000字,csdn是直接复制本地md文件的,所以图片无法展示,大家可以下载本地版本的。本地版本提供了两种,pdf和md,方便查看和修改。压缩包里面包含了Vue2以及Vue3的笔记,欢迎大家下载本地版。
链接: https://pan.baidu.com/s/1IVcwuouu2eIpwDUyAZfong?pwd=3306 提取码: 3306
--来自百度网盘超级会员v3的分享
Vue框架
Vue是什么?
一套构建用户界面的渐进式javascript框架
渐进式:一个轻量小巧的核心库,可以引入各种插件。
开发者
Vue的特点:
1.采用组件化模式,提高代码复用率,且让代码更好维护。
2.声明式编码,让编码人员无需直接操作DOM,提高开发效率。
命令式编码:需要每一步都由开发人员自己指定
声明式编码:相当于将一堆命令封装成一个功能,要使用这个功能的时候只需要声明一下就可以替代多个命令。
第一个Vue案例
下载vue.js,然后通过<script type= "text/javascript" src ="vue.js"></script>将vue导入到本项目中
<script type= "text/javascript" src ="vue.js"></script> 引入开发的Vue <div id="app"> <h1>helloworld,{ {name.toUpperCase()}}</h1> </div> <script> Vue.config.productionTip = false new Vue({ el: "#app", data: { name: "shangguigu", age: 12 } }); </script>
总结:
初始Vue: 想让Vue工作,必须创建一个Vue实例,并且要传入一个配置对象。 Root容器里面的代码依然符合HTML规范。只不过混入了一些特殊的vue语法。 root容器里的代码被称为Vue模板 Vue实例和容器是一一对应的 真实开发中只有一个Vue实例,并且会配合着组件使用 { {xx}}中的xxx要写js表达式,并且xxx可以自动读取到data中的所有属性 一旦data中的数据发生了改变,那么模板中用到该数据的地方也会自动更新。
模板语法
Vue容器中的东西就是一个模板,插值语法和指令这些都是模板语法。
插值语法
{ {}}
插值语法:用于指定标签体内容,{ {}}里面写js的表达式,就是将指定的内容写在指定的位置。
<script type= "text/javascript" src ="vue.js"></script> 引入开发的Vue <div id="app"> <h1>helloworld,{ {name.toUpperCase()}}</h1> </div> <script> Vue.config.productionTip = false new Vue({ el: "#app", data: { name: "shangguigu", age: 12 } }); </script>
指令语法
v-bind
指令:可以给标签中的任意一个属性动态的绑定属性,单向绑定。指令语法用于指定标签属性,标签体内容,绑定事件
<div id="root"> <h1>hello,{ {usernam}}</h1> <a v-bind:href="url.toUpperCase()" :helo="x">“”中是js表达式</a> </div> <script> Vue.config.productionTip = false; const x = new Vue({ el: '#root', data: { usernam: 'world', url: 'http://baidu.com', x: 10 } }) </script>
插值语法和指令语法使用总结
Vue模板语法有两大类: 1、插值语法: 功能:用于解析标签体内容。 写法:{ {xx}},xx是js表达式,并且可以读取data中的所有属性。 2、指令语法: 功能:用于解析标签(包括标签属性,标签体内容、绑定事件)。 举例:v-bind:href="xxx" 简写 :href ="xxx" xxx同样是js表达式,并且可以直接读取data中的所有属性 备注:Vue中有很多睡醒,形式都是v-???。
v-model
和v-bind一样,不同的是v-module数据双向绑定。
<div id="app"> <!-- 数据绑定 --> 单向数据绑定:<input type="text" v-bind:value="name" /> 双向数据绑定:<input type="text" v-model:value="name" /> 简写 单向数据绑定:<input type="text" :value="name" /> 双向数据绑定:<input type="text" v-model="name" /> </div> <script> Vue.config.productionTip = false; var s = new Vue({ el: '#app', data: { name: '湖南职业学院' } }); // v.$mount('#app'); // console.log(s) </script>
v-module只能应用在表单类元素上(输入类元素) Vue中有两种数据绑定的方式。 单向绑定数据只能从data流向页面。 双向绑定数据不仅能从data流向页面,还能从页面流向data。 备注: 双向绑定一般都应用在表单类元素上。Input select v-module:Value,可以简写为v-module,value可以省略,因为v-module默认收集的就是value值。
el和data的两种写法
el和data都有两种写法。
el的第一种绑定方式: var s = new Vue({ el: '#app', } 第二种: var s = new Vue({ } s.$mount('#app') 两种都可以用,第二种更灵活一些。 Vue对象中含有很多$开头的函数,都是给程序员用的,其他的是给给底层用的。
data的第二种写法:
var s = new Vue({ el: '#app', // data: { // name: 'hunankeji' // //data的第二种写法:函数式写法 // }, data() { console.log('$$$', this) //这里的函数是vue调用的,this是vue实例对象,data返回一个函数,函数的返回值是我们真正要的对象。 return { name: 'hunankeji' } } });
总结
data和el有两种写法: el两种写法: 1、new Vue的时候配置el属性 2。写创建Vue实例,随后通过vm.$mount('#root')指定el的值 data两种写法: 对象式:data:{} 函数式:data:function(){}组件的时候必须返回一个函数,否则出错 注意:由Vue管理的函数一定不要写箭头函数,如一旦写了箭头函数,this的指向就不再是vue实例了。
何选择呢目前用哪种写法都可以以后学到组建的时候各位data必须用函数式否则会报错那个时候我会详细的给你讲解为什么他要有这个限制好同学有一个最重要的原则我们发 朱老师
MVVM模型
M:module(模型):对应data中的数据
V:视图(View):模板,也就是页面,Vue里面的那个容器
VM: 视图模型(ViewModel):Vue实例对象
最后都出
data身上所有的属性都出现vm上 vm身上的所欲属性以及原型上的所有属性,vue模版中可以直接使用。
数据代理
什么是数据代理:
所谓数据代理就是通过一个对象去代理对另一个对象的属性的操作(读/写) 比如现在有:per1 per2 两个对象 ,通过per2对象去操作per1对象中的x属性,这个就是数据代理
let per1 = { x: '张三' } let per2 = { y: '李四' } // 所谓数据代理就是通过一个对象去代理对另一个对象的属性的操作(读/写) // 比如现在有:per1 per2 两个对象 ,通过per2对象去操作per1对象中的x属性,这个就是数据代理 Object.defineProperty(per2, 'x', { get() { return per1.x //当访问per2的x属性的时候,实际上访问的是per1属性的x值 }, set(value) { per1.x = value } })
通过数据代理我们可以通过per2去操作per1的属性。
defineProperty方法
defineProperty方法是Object对象中的,该方法用于给指定的对象定义属性,并且实现对属性的操作。比如遍历,修改,删除等操作。同时该方法也是vue实现数据绑定的关键方法。
语法:
Object.defineProperty(指定对象,属性名,{配置项})吧
let num = 15; let person = { name: '张三', sex: '男', // age: num } { { this.$route.query.val }} Object.defineProperty(per, 'age', { // value: 12, //通过value向对象的属性赋值,该属性默认不支持枚举 // enumerable: true, //控制属性是否可以枚举,默认false。 // writable: true, //控制属性是否能被修改,默认false // configurable: true //控制属性是否能被删除 ,默认false // 当有人读取person的age属性时,该get函数(getter)就会被调用,并且返回值就是age的值 get: function () { // get配置 console.log('有人读取了age属性'); return num //返回值就是age属性的值 }, // 当有人修改person的age属性时,set函数(setter)会被调用并且修改age的值。 set(value) { console.log("有人修改了age属性并且值是", value); num = value; //将num的值修改为传入的值 } }) // console.log(Object.keys(per)) //输出指定对象中的所有属性名 console.log(per)
get函数是defineProperty()中的一个配置项,当有人对指定的属性进行读取的时候get函数就会被调用,并且返回值就是指定的属性的值。相当于取代了value配置项的作用。该函数最大的好处就是实现数据绑定,当我们访问属性的时候会重新给该属性赋值,从而使得数据是更新的。
// 当有人读取per的age属性时,该get函数(getter)就会被调用,并且返回值就是age的值 get: function () { // get配置 console.log('有人读取了age属性'); return num //返回值就是age属性的值 }
set函数是当指定的属性被修改的时候调用并且将指定属性的值进行修改。
// 当有人修改person的age属性时,set函数(setter)会被调用并且修改age的值。 set(value) { console.log("有人修改了age属性并且值是", value); num = value; //将num的值修改为传入的值 }
通过defineProperty将变量和对象中的属性进行了绑定,getter和setter实现了属性的现用现取,当访问到该属性的时候调用get函数获取属性的值,当修改属性的时候调用setter给属性赋值。实现了现用现取。
在浏览器控制台查看对应的属性的值的时候可以发现:
通过defineProperty方法添加的属性并不会立即显示在控制台,我们将鼠标悬停在...上可以看到“=调用属性 getter”,表示的就是调用getter给age进行赋值。体现的就是现用现赋。
<script> // 数据代理 let a = {x : 10} let b = {y : 100} Object.defineProperty(b,'x',{ get(){ return a.x }, set(value){ a.x = a.x; } }) </script> 代理就是通过对对象a的x修改,实现对对象b的x属性的操作。
Vue中的数据代理
Vue中的数据代理就是Vue实例中的数据和data对象中的数据之间的动态代理
//创建爱你一个Vue实例 const vm = new Vue({ el:'#app', data:{ name:'make', age : 14 } }) console.log(vm);// 输出Vue实例
输出vue,可以看到data中的数据出现在了v m实例上。
通过控制台输出的vm实例可以发现,在vue实例中存在name和age属性并且值和data对象中的保持一致,也就是说vm代理了data对象中的属性。
Vue的动态代理实现的是_data中的数据和vm实例之间的代理。通过vm实例可以操作_data中的数据。而_data就是我们在代码中写的data。作用就是简化了操作,我们可以直接操作data中的数据,不需要使用_data.xxx的方式操作data中属性。
1.Vue中的数据代理: 通过vm对象来代理data对象中属性的操作(读/写) 2.Vue中数据代理的好处: 更加方便的操作data中的数据 3.基本原理: 通过Object.defineProperty()把data对象中所有属性添加到vm上。 为每一个添加到vm上的属性,都指定一个getter/setter。 在getter/setter内部去操作(读/写)data中对应的属性。
Vue中的数据代理图解:
事件处理
vue事件处理使用的是指令。
v-on:click(点击事件)
表示的是点击事件。
语法:v-on:click="函数(参数值)"
v-on:mouseover(鼠标悬停事件)
如何传递参数并保留event:
<button @click="showInfo2(33,$event)">点我试试</button>
$表示的是js给我们传递的参数,使用$event向函数中传递默认的js的参数。
event属于默认的参数,可以传递也可以不传递,不传递也可以使用event.
点击事件会默认给我们传递一个参数,event,表示的当前这个点击事件。包括坐标,内容等等一系列的参数。
事件event对象是指在浏览器中触发事件时,浏览器会自动创建一个event对象,其中存储了本次事件相关的信息,包括事件类型、事件目标、触发元素等等。浏览器创建完event对象之后,会自动将该对象作为参数传递给绑定的事件处理函数,我们可以在事件处理函数中通过访问event对象的属性和方法,来获取事件的相关信息,并做出后续的逻辑处理。
<script src="../js/vue.js"></script> <div id="root"> <button v-on:click="showInfo">点我显示信息</button> <button @click="showInfo">点我显示信息(简写形式)</button> <button @click="showInfo1(22,$event)">点我显示信息(传参)</button> $event是vue提供的类似于关键字占位符,会给event占位,这样event就不会丢失了。 </div> <script> new Vue({ el: "#root", data: { name: "张三", age: 10 }, methods: { showInfo(event) { console.log(this) //这里的this是vm,不能使用箭头函数,因为箭头函数没有this,向上走就到了window console.log(event.target.innerText) }, showInfo1(number,event){ console.log("通讯号",number) console.log(event.target.innerText) } }, }) </script>
结果:
总结:
事件的基本使用: 使用v-on:xx或者@xx绑定事件,其中xx是事件名 事件的回调需要配置在methods对象中,最终会在vm上。 methods中配置的函数,不要用箭头函数,否则this不是vm methods中配置的函数,都是被Vue所管理的函数,this是指向vm或者组件实例对象 @click="demo" 和@click ="demo($event)"效果一致,但是后者可以传参
事件修饰符
prevent修饰符
用于阻止超链接的默认跳转行为。
原始方式:
<a href="http://www.baidu.com" @click="showInfo">点击试试</a> showInfo() { event.preventDefault(); //阻止超链接的默认行为 alert("试试就试试") } }
Vue方式:
<a href="http://www.baidu.com" @click.prevent="showInfo">点击试试</a>
stop修饰符
阻止事件冒泡。什么是事件冒泡?当存在两个盒子a和b,其中b在a盒子中。给a盒子和b盒子都绑定一个单机响应事件,因为b在a盒子内部,所以当我们点击b盒子的时候也会点击到a盒子,这样就会同时执行了两次点击事件,这并不是我们需要看到的,,我们点击b就只执行
b的事件即可。
原始方式:
showInfo() { event.stopPropagation();//阻止事件冒泡 alert("试试就试试") }
Vue方式:
<!-- 阻止事件冒泡 --> <div class="test" @click="showInfo"> <button @click.stop="showInfo">点击事件</button> </div>
once修饰符
事件只触发一次。
存在一个按钮,当点击一次的时候进行弹窗,当第二次第三次的时候就不会在触发效果。
<!-- 事件只触发一次 --> <button @click.once="showInfo">点击事件</button>
capture修饰符
使用事件的捕获模式。
事件流阶段分为:捕获阶段,目标阶段,冒泡阶段。
捕获阶段 (从根节点开始顺着目标节点构建一条事件路径,
即事件由页面元素接收,逐级向下,到具体的元素
)目标阶段 (到达目标节点,
即元素本身
)冒泡阶段 (从目标节点顺着捕获阶段构建的路径回去,
即跟捕获相反具体元素本身,逐级向上,到页面元素
)
该修饰符可以让事件在捕获阶段就开始执行。点击事件默认是冒泡阶段才开始执行。
<!-- 使用事件的捕获模式,加在外层表示该事件在捕获阶段就执行 --> <div class="box1" @click.capture="showmsg(1)"> box1 <div class="box2" @click="showmsg(2)"> box2 </div> </div> showmsg(a){ console.log(a) }
也就是说默认是先执行里面的事件,在执行外面的事件。现在要修改顺序,先执行外面的,再执行里面的。
self修饰符
只有event.target是当前操作的元素时才触发事件。
这个属性也能阻止冒泡,什么意思呢?就是当这个事件是自己这个元素上的才会生效。
比如有两个div1, div2 。div2在div1里面。两个div都绑定一个事件(执行同一个函数 )。我们点击div2的时候就会出现事件的冒泡
<div id="app"> <div class="div1" @click.self="showInfo('1')"> div1 <div class="div2" @click="showInfo('2')">div2</div> </div> </div> <script> new Vue({ el: '#app', data: {}, methods: { showInfo(val) { console.log(event.target.innerHTML) alert(val) }, } }) </script>
passive修饰符
事件的默认行为立即执行,无需等待事件的回调执行完毕
键盘事件
传统方式获取到键盘中的按键:
showInfo() { console.log(event.keyCode); if (event.keyCode == 13) { //当键盘中对应的编码是13的时候弹出提示 alert("我是战三") } }
Vue方式:
通过使用别名的方式实现键盘事件
<input type="text" name="code" @keyup.enter="showInfo" /> @keyDown
常见的键盘别名:
enter -- >回车
delete --> 删除/delete键
esc-->退出
space-->空格
tab --> 换行
up-->上
down-- >下
left-->左
right-->右
如果我们需要使用其他的按键的时候可以使用按键真实的名字,上述都是vue为部分常用按键取的别名。
获取按键的名字和对应的编码。
console.log(event.key, event.keyCode)//按键的名字
需要注意的是比如:CapsLock (切换大小写)按键是由多个单词拼接而成的,使用的时候需要将大写全部转换成小写,单词之间使用-进行分隔
<input type="text" name="code" @keyup.caps-lock="showInfo" />
特殊的键
Tab键
因为Tab键本身存在了失去焦点的功能,当我们使用tab键完成keyup事件的绑定的时候,不等键弹起焦点就会失去,就无法触发事件,所以使用tab的时候使用keydown,当键盘按下就出发事件,不需要抬起键盘中的键。
系统修饰键
Ctrl键 ,alt,shift,meta(win)
配合keyup使用的时候:按下修饰键的同时,按下其他的键,再松开其他的键,事件才会被触发。
配合keydown使用:正常触发事件。
系统修饰键后面可以再跟一个键作为组合使用。
<input type="text" name="code" @keydown.ctrl.y="showInfo" />
第三种方式绑定键盘事件:
使用对应键盘的编码进行绑定,不支持。因为不同的键盘可能存在编码不同的情况。
<input type="text" name="code" @keydown.13="showInfo" />
自定义键盘的别名按键
Vue.config.keyCodes.huiche = 13; //自定义别名 //使用自定义别名 <input type="text" name="code" @keydown.huiche="showInfo" />
键盘事件可以同时存在多个:
//阻止默认行为并且阻止冒泡 <a @click.stop.prevent="showInfo">点击事件</a> 同时存在多个事件修饰符
计算属性
计算属性-computed
什么是计算属性?
定义:当要用到的属性并不存在,需要通过已有的属性计算才能获的。
原理:底层借助了Object.defineproperty方法提供的getter和setter。
get函数什么时候执行?
1.初次获取时或执行一次
2.当依赖的数据发生了变化的时候会被再次调用
优势:与methods相比实现类内部缓存机制,效率更高,调试更加方便
备注:
1.计算属性最终会出现在vm上,直接读取即可。
2.如果计算属性要被修改,那么必须编写set函数去响应修改,且set中要引起计算时依赖的数据发生变化。
只要data:{}中的数据发生变化Vue就会重新解析模板,如果模板中插值语法中的是一个函数{ {getName()}},这个函数在重新解析模板的时候就会被重新调用。data中的数据发生了变化函数就会被重新调用。
<script src="../js/vue.js"></script> <div id="root"> 姓:<input type="text" v-model:value="xing" name="xing"> <br/> 名:<input type="text" v-model="ming" name="ming"><br/> 全民:{ {fullName}} </div> <script> Vue.config.productionTip = false; const vm = new Vue({ el: '#root', data: { xing:'张', ming:'名' }, // 有人调用fullname的时候,get就会被调用,并且做了缓存,即使多次调用同一个计算属性,get方法也只会执行一次。并且返回值就是fullname的值,get函数中的this指向的是vm,data中的数据都出现在vm身上。所以我们可以直接通过this得到data中的属性。 computed:{ fullName:{ get(){ console.log("当有人调用了fullName,那么get方法就会被调用") return this.xing+"-"+this.ming }, set(value){ console.log("zhangsan") const arr =value.split('-'); this.xing = arr[0]; this.ming = arr[1]; } } } }) </script>
简写,当我们使用计算属性只读不写的时候可以使用简写方式。只考虑读取的时候使用。也就是说如果只有计算属性只有get函数,那么就不用写成对象形式。直接写成函数形式,这个函数表示的就是get函数。
computed: { //简写,这个函数代表的就是get函数 funllName: function () { } //再次简写:这里的fullName表面上是一个函数,实际上vm上面的是fullName是该函数执行之后的结果赋值到fullName上。 funllName(){ } }
事件中的小技巧
div中是vue的模板,在模板中可以读取到vue实例中的内容。data中的数据会出现在vue实例上,所以可以直接在模板中操作vue实例中的数据。但是不能直接操作window中的数据,因为vue实例中并不存在window,比如alert('zhangan');因为alert是window中的函数, 不会出现在vue实例中。
<div id="app"> <!-- <h1>今天天气很{ {isHot?'炎热':'寒冷'}}</h1> --> <h1>今天天气很{ {info}}{ {x}}</h1> <!-- 特殊写法,绑定事件的时候@click="yyy" yyy是一些简单的语句--> <!-- <button @click="isHot = !isHot;x++">改变天气</button> --> <!-- 正常写法 --> <button @click="changeWeather">改变天气</button> </div> <script> Vue.config.productionTip = false //阻止vue启动时生成生产提示信息 new Vue({ el: '#app', data: { isHot: true, x: 1 }, methods: { changeWeather: function () { this.isHot = !this.isHot } }, computed: { info() { return this.isHot ? '炎热' : '寒冷' } } }) </script>
监视(侦听)属性
作用:监视某个属性的变化
使用watch配置项,可以监视data中的属性,也可以监视计算属性中的属性。
回调函数:
回调函数就是传递一个参数化函数,就是将这个函数作为一个参数传到另外一个主函数里面,当那个主函数执行完 之后,再执行传递过去的函数,这里面的作为参数传递的函数就是回调函数。
监视属性watch:
当被监视的属性发生变化的时候,回调函数自动调用,进行相关的操作
监视的属性必须存在,才能进行监视!!
监视的两种写法:
通过watch配置项进行配置
通过wm.$watch监视
watch: { // isHot:表示被监视的属性,全写'isHot' isHot: { // 立即执行,初始化时让handler调用一次,就是启动的时候就执行一次 immediate: true, // handler函数:当isHot发生变化的时候被调用 handler(newValue, oldValue) { //两个属性表示oldValue修改之前的值,newValue表示新的值 console.log("被修改了", newValue, oldValue) } } } ------------------第二种写法--------------------------------------------------- vm.$watch('isHot', { //'isHot' 表示的是被监视的属性名,如果不加引号报错:因为{}表示的是对象,对象中的属性是键值对的形式,’key‘:value.真实写法是带有引号的,但是可以简写时不带引号。但是当我们在{}外面调用该属性的时候就必须使用引号,因为只有{}里面才能进行简写。并且当属性的一个对象,且我们要使用的是该对象中的属性的时候,需要使用引号。 // 立即执行,初始化时让handler调用一次 immediate: true, // handler函数:当isHot发生变化的时候被调用 handler(newValue, oldValue) { console.log("被修改了", newValue, oldValue) } })
深度监视
深度监视:
Vue中的watch默认是不监测对象内部值的变化的改变(一层)
配置deep:true可以监测对象内部值的改变(多层)
备注:
Vue自身可以监测对象内部值的改变,但是Vue提供的watch默认不支持多层监测。
使用watch时根据数据的具体结构决定是否进行深层监测
当我们监视的属性是一个对象时,如何监视对象中的每一个属性:需要使用deep(深的),表示的是监视该对象属性中的所有属性。deep写在监视器的内部。
watch: { //开启深度监视,监视多级属性中所有属性的变化。 ishot:{ immediate:true, deep:true, handler(newValue,oldValue){ console.log("张三",newValue) } }, //number属性里面有一个a属性,监视多级属性中某个属性的变化。 'number.a':{ handler(){ } } } //使用挂载的方式进行属性监视 vm.$watch('isHot',{ immediate:true, deep:true, handler(newValue, oldValue) { console.log('被修改了', newValue, oldValue) } } )
监视属性简写
当被监测的属性只有一个handler的时候可以进行简写,不需要immediate和deep这两个配置项的时候就可以进行深度监视。
watch: { isHot(newValue, oldValue) { console.log("被修改了", newValue, oldValue) } ----------------------------------第二种方式的简写 vm.$watch('isHot', function (newValue, oldValue) { console.log('被修改了', newValue, oldValue) })
计算属性和监视属性区别
使用监视属性的话必须先在data定义好变量才能操作,并不会在开发者工具中展示为监视属性的。
计算属性的结果完全是靠返回值,我们如果想要将结果等等在返回无法在计算属性中做到,在监视属性中我们使用延时器可以延迟修改
<div id="root"> 姓:<input type="text" name="" v-model="firstName" id=""> <br> 名:<input type="text" name="" v-model="lastName" id=""> <br> 全名:{ {fullName}} </div> --------------------------------------------------------------- 计算属性实现全名案例 <script> new Vue({ el:"#root", data:{ firstName:'张', lastName:'三', }, computed:{ fullName:{ get(){ return this.firstName+"-"+this.lastName; } } } }) </script> --------------------------------------------------------- 监视属性实现全名案例 <script> new Vue({ el:"#root", data:{ firstName:'张', lastName:'三', fullName:'张-三' }, watch:{ firstName:{ handler(newValue){ this.fullName = this.firstName+"-"+this.lastName; } }, lastName:{ handler(newVlaue){ this.fullName = this.firstName+"-"+this.lastName; } } } }) </script> 两者都能实现功能,但是监视属性能够灵活的进行操作,计算属性是通过返回值指定值的,不能再内部写回调,监视属性可以定义回调.比如下面延时1秒再赋值操作。 watch:{ firstName:{ handler(newValue){ 所有被vue所管理的函数最好写成普通函数,这样this就是vm或者组件实例对象 所有不是Vue所管理的函数最好写成箭头函数,这样this就是vm或者实例对象 setTimeout(()=>{ this.fullName = this.firstName+"-"+this.lastName; },1000) } }, lastName(newVlaue){ this.fullName = this.firstName+"-"+this.lastName; } } }
computed和watch之间的区别: 1.computed能够完成的功能,watch都可以完成 2.watch能够完成的功能,computed不一定能够完成,比如watch的异步操作。 两个重要原则: 所有被vue所管理的函数最好写成普通函数,这样this就是vm或者组件实例对象 所有不被Vue所管理的函数(定时器回调,ajax的回调函数,Promise的回调函数等)最好写成箭头函数,这样this就是vm或者实例对象。因为如果不是vue管理的函数,写成普通的this就是window,但是我们要在函数里面用vm上的东西就拿不到。
箭头函数和普通函数的区别
箭头函数语法:
(x) => x + 6
x:函数的形参
x+6:函数的返回值
相当于:
function(x){ return x + 6; }
1.外形不同,箭头函数使用箭头定义
// 普通函数 function func(){ // code } ----------------------------- // 箭头函数 let func=()=>{ // code }2.箭头函数都是匿名函数,普通函数存在匿名函数,也有非匿名函数。
// 具名函数 function func(){ // code } // 匿名函数 let func=function(){ // code } // 箭头函数全都是匿名函数 let func=()=>{ // code }3.箭头函数不能用来作为构造函数,不能new对象
function Person(name,age){ this.name=name; this.age=age; } let admin=new Person("恩诺小弦",18);4.箭头函数中的this指向不同,箭头函数本身没有this,但是它在声明时可以捕获其所在上下文的this供自己使用。
var webName="捕获成功"; let func=()=>{ console.log(this.webName); } func(); 结果:捕获成功其他区别:
(1).箭头函数不能Generator函数,不能使用yeild关键字。 (2).箭头函数不具有prototype原型对象。 (3).箭头函数不具有super。 (4).箭头函数不具有new.target。
计算属性和监视属性的区别
computed 能够完成的watch都能够完成
watch能够完成的功能computed不一定能够完成,比如watch能够进行异步操作
两个重要的原则:
所有被vue管理的函数最好都写成普通函数,这样this指向的才是vm 或者组件实例对象
2.所有 不被Vue管理的函数(定时器的回调函数,ajax的回调函数等) 最好写成箭头函数,这样this指向的才是vm 或者 组件实例对象
样式绑定
Vue绑定class样式
使用vue进行class样式绑定--字符串写法。适用于:样式的类名不确定的情况,需要动态指定。
使用v-bind对class属性进行绑定。固定的class属性使用普通的写法,需要动态变化的class使用v-bind进行动态绑定.
<div class = "basic" :class="test"></div>
使用这个时候vue会对:class进行解析,最终获取到:class:'basic test'。
第一种情况-字符串写法:当要绑定的类名不确定的时候,需要动态指定的时候。
下面的案例中就是要读取的东西是从mood属性中得到的,这个mood是可以修改的。是不确定的。吧
案例:点击div修改样式
使用:class绑定data中的属性,这个属性的属性值是css样式。 <style> .normal{ color:red, border:10px } </style> :class是变化的,以绑定的形式写v-bind简写形式 <div class="basic" :class="mood" @click=“changedMood”> { {name}} </div> <script> new Vue({ el:'#app', data:{ name:'尚硅谷', mood:'normal' }, methods:{ changeMood(){ this.mood="happy" } } }) </script>
第二种情况:数组写法,适用于要绑定的样式个数不确定,名字也不确定的情况,通过数组下标选择.
从一堆数组中随机取出一个样式绑定到div中。
<div class="basic" :class="mood",@click="changeClass"> //这个时候同时绑定了三个class属性 { {name}} </div> <script> new Vue({ el:'#app', data:{ name:'尚硅谷', classArr:['class1','class2','class3'], mood:'nomal' }, methods:{ changeClass(){ const index = (Math.floor(Math.random()*3) this.mood=this.classArr[index] } } }) </script> ---------------如果要绑定的样式会变化,可以交给vue处理-------------------------- 直接将vue中定义的样式名数组放到:class上,这样就可以运用全部的样式了,如果想对样式进行修改,那么只需要通过vm.classArr.shift() /put()对数组进行操作即可实现动态增改操作 <div class="basic" :class="classArr",@click="changeClass"> //这个时候同时绑定了三个class属性 { {name}} </div> <script> new Vue({ el:'#app', data:{ name:'尚硅谷', classArr:['class1','class2','class3'], mood:'nomal' } }) </script>
第三种情况:对象写法;适用于要绑定的个数确定,要绑定的名字确定,但是要动态决定是否使用的时候。
<style> .classa{}, .classb{} </style> <div class="basic" :class="classObj"> //这个时候同时绑定了三个class属性 { {name}} </div> <script> new Vue({ el:'#app', data:{ name:'尚硅谷', classArr:['class1','class2','class3'], classObj:{ classa:false, //样式名:false/true,true就表示应用,false表示不用。 classsb:false } } }) </script>
Vue绑定style样式
要求样式中出现-的使用驼峰命名,font-size修改为fontSize,单位使用引号,和数字使用+相加。
<div class="basic" style="{fontSize : fsize+'px'}"> //这个时候同时绑定了三个class属性 { {name}} </div> <script> new Vue({ el:'#app', data:{ name:'尚硅谷', fsize:40 } }) </script> ------------------------------- <div class="basic" :style="styleObj"> //这个时候同时绑定了三个class属性 { {name}} </div> <script> new Vue({ el:'#app', data:{ name:'尚硅谷', styleObj:{ fontSize:'40px' } } }) </script> -------------- 数组形式 :style="[styleObj,styleObjec1]"; <script> new Vue({ el:'#app', data:{ name:'尚硅谷', styleObj:{ fontSize:'40px' }, styleObjec1:{ background:red; } } }) </script>
绑定样式总结:
1.class样式:
写法:class=“xxx” xxx是字符串 ,对象 ,数组
字符串写法适用于:类名不确定,要动态获取
对象写法适用于:要绑定多个样式,个数不确定,名字不确定
数组写法适用于:要绑定多个样式,个数确定,名字确定,但是不确定是否使用
2.style样式
:style:“{fontSize:xxx}” 其中xxx是动态的值
:style=“a,b,c”其中a,b是样式对象(在data中的对象并且属性是css中存在的样式值)
样式对象:data:{
styleObj:{
color:'red'
}
}
条件渲染
v-show指令
并不是删除,结构还是存在的。
v-show是一个指定,用于定义一个元素是否展示,true表示展示,false表示隐藏。false的时候实际上就是添加了该:display:none。
<h1 v-show="false">欢迎来到{ {name}}</好> <h1 v-show="true">欢迎来到{ {name}}</好>
v-show=""指令双引号之间可以是一个表达式或者是一个变量
<div id="app"> <h1 v-show="false">欢迎来到{ {name}}</h1> <h1 v-show="a">欢迎来到{ {name}}</h1> <h1 v-show="1===1">欢迎来到{ {name}}</h1> </div> <script> const vm = new Vue({ el: '#app', data: { name: '湖南科技', a: true } }) </script>
v-if指令
用于条件判断,当不满足v-if中的条件的时候,该元素直接被删除。
v-else-if指令
v-else-if和v-if是一组指令,这两个可以一起使用,作用相当于if else if语句 条件判断
<div v-if="n==1">Re</div> <div v-else-if="n==2">Es</div> <div v-else-if="n==3">Fs</div>
v-else指令
该指令中不需要条件,当前面的if else if都不满足条件的时候else执行。
使用v-if v-else-if v-else三个指令共同工作的时候不能打断,必须紧紧挨着。
template的使用
template不会改变原来的结构,在浏览器中<template>不会被解析。只能配合v-if使用,不能和v-show使用。
// 这种方式可以同时对三个h1元素进行操作,但是会改变结构。多一个div <div v-if="n==4"> <h1>张三</h1> <h1>李四</h1> <h1>王五</h1> </div> <template v-if="n===5"> <h1>张三</h1> <h1>李四</h1> <h1>王五</h1> </template>
列表渲染
v-for指令
相当于js中的for...in...语法。用于遍历。
语法:
v-for="(变量名,下标) in 被遍历的数组或者对象" :key="唯一值"
变量中存储的是数组中每一个值或者对象中的每一个属性
key是给被遍历的每一个li指定一个唯一的key
in 也可以写为of
<div id="app"> <ul> <li v-for="(per,index) of persons" :key="index"> { {per.id}}----{ {per.name}}---{ {index}} </li> </ul> </div> <script> new Vue({ el: '#app', data: { persons: [ { id: '001', name: '张三', sex: '男' }, { id: '002', name: '李四', sex: '女' }, { id: '003', name: '王五', sex: '男' } ] } }) </script>
v-for遍历对象
语法:
<li v-for="(value,k) in car" :key="k">
value:表示的是对象的属性值
k:表示的是对象的属性名
<ul> <li v-for="(value,k) in car" :key="k"> { {c}}--{ {index}} </li> </ul> car: { name: '宝马', price: 100, color: '黑色' }
v-for遍历字符串
语法:
v-for="(char,index) in str" :key="index"
char:单个字符
inde:下标(索引)
<!-- 遍历字符串 --> <ul> <li v-for="(char,index) in str" :key="index"> { {char}}--{ {index}} </li> </ul> str: 'helloworld'
v-for指令遍历指定次数
语法:
v-for="(number,index) in 5" :key="index"
number:从1-指定次数的值
index:下标,从0开始
1--0
2--1
3--2
4--3
5--4
<!-- 遍历指定次数 --> <ul> <li v-for="(number,index) in 5" :key="index"> { {number}}--{ {index}} </li> </ul>
key的作用和原理
当我们使用index下标作为:key的值的时候,流程演示。
真实数据首先会被vue转换成虚拟dom,该虚拟dom是vue内部使用,用户不能直接访问。
然后再将虚拟dom转换成真实dom。
当原始数据发生变化的时候,就需要再次生成虚拟dom,这次生成的虚拟dom并不会直接变成页面中的真实dom。
而是通过虚拟dom算法,以key的值为标准,将原始的虚拟dom和新生成的虚拟dom进行对比。
如果发现了新的虚拟dom中的节点和原始的dom节点一致。就直接使用原来虚拟dom生成的真实dom作为新的虚拟dom所对应的真实dom。
以下标作为key的值的时候存在的问题:
影响效率问题,出现数据混乱
当以p.id作为key的值的时候
两者出现问题的根本原因是因为index下标是会因为index会随着数组中的元素发生变化而所指向对象也发生变化。而p.id始终指向固定的对象。
第三种情况:当我们使用v-for进行遍历的时候没有写:key 会默认的将数组的下标作为key使用。
key是遍历中的每一条数据的唯一标识。
向数组最前面加上一个数据
const arr = [ {id:1,name:'张三'}, {id:2,name:'李四'}, {id:3,name:'王五'} ] //我们要在数组arr最前面添加一个对象、 const per = {id:4,name:'赵六'} this.arr.unshift(per) //向数组的第一个位置添加该数据 this.arr.put(per) // 向数组的最后一个位置添加数据
不同的场景适应情况不同。如果是向最后一个位置添加新的对象不会出现问题,因为index所指向的对象和原来指向的对象是一样的。
Key的作用
虚拟DDOM中key的作用:
key是虚拟dom对象的标识,当数据发生变化时,vue会根据[新数据]生成【新的虚拟DOM】,随后Vue进行[新的虚拟DOM]与旧的【虚拟DOM】进行差异比较。
比较规则:
1.旧虚拟DOM中找到了与新的虚拟DOM相同的Key:
(1)如虚拟ODM中内容没有变化,则直接使用之前的真实DOM
(2)如果虚拟DOM中内容变化了,则生成新的真实DOM,随后替换掉页面中 之前的真实DOM.
2.旧的虚拟DOM中未找到与新的虚拟DOM相同的key
创建新的真实DOM,弱后渲染到页面。
用index将作为key可以能会引发的问题:
若对数据进行:逆序添加,逆序删除等破坏顺序的操作:
会产生没有必要的真实DOM更新==>界面效果没有问题,但是效率低。
如果结构中包含了输入类的DOM
会产生错误的DOM更新==》界面出错
开发中如何选择key:
做好使用每条数据的唯一标志作为key,比如id,手机号,身份证号,学号等唯一标识。
如果不存在对数据的逆序添加,逆序删除等破坏顺序的操作,仅仅用于渲染类表用于展示,可以使用index作为key的值
列表过滤(模糊搜素)
监听属性实现模糊
要求:存在一个数组,数组中保存着多个对象,通过搜索框的模糊搜索实现对象信息的展示
效果图
监听属性实现模糊查询思路:
将一个属性和输入框绑定,动态获取输入框中的值。
通过监听属性监听与输入框绑定的属性,当发现属性值发生变化的时候,实现实时响应。
通过数组的filter方法实现条件过滤,满足条件的对象被过滤出来返回一个新的数组。
在模板中遍历的是并不是存储了原本的数据的数组,而是存储了过滤之后数据的数组。
<body> <div id="app"> <h1>人员列表</h1> <input type="text" placeholder="请输入名字" v-model="keyword"> <ul> <li v-for="(per,index) of filPersons" :key="index"> { {per.sex}}----{ {per.name}}---{ {per.age}} </li> </ul> </div> <script> new Vue({ el: '#app', data: { keyword: '', persons: [ { id: '001', name: '马冬梅', age: 23, sex: '女' }, { id: '002', name: '周冬雨', age: 21, sex: '女' }, { id: '003', name: '周杰伦', age: 33, sex: '男' }, { id: '004', name: '温兆伦', age: 32, sex: '男' } ], filPersons: [] }, watch: { keyword: { immediate: true, //开启不修改执行一次,因为名字中包含了空串,所以什么都没有输入的时候就是空串,只要在第一次执行的时候开启监听就能获取到所有的值 handler(val) { this.filPersons = this.persons.filter((per) => { return per.name.indexOf(val) !== -1 }) } } } })
计算属性实现列表过滤
computed: { filPersons() { return this.persons.filter((p) => { return p.name.indexOf(this.keyword) !== -1 }) } }
数组中的filter方法
作用:用于过滤数组中的特定条件,filter方法不会改变原数组,而是返回一个新的数组!!!
语法:
array.filter(function(currentValue,index,arr), thisValue)
currentValue: 当前元素
index:当前元素索引
arr:当前元素所属数组
thisValue:作为参数中的function中的this
使用箭头函数的方法:
var newArr = arr.filter(obj => !obj.flag);例子:
data: { keyword: '', persons: [ { id: '001', name: '马冬梅', age: 23, sex: '女' }, { id: '002', name: '周冬雨', age: 21, sex: '女' }, { id: '003', name: '周杰伦', age: 33, sex: '男' }, { id: '004', name: '温兆伦', age: 32, sex: '男' } ] }, watch: { // 对keyword属性进行监听 keyword(val) { //val表示新的值 this.persons = this.persons.filter((per) => { //使用array中的filter函数进行过滤 return per.name.indexOf(val) !== -1; //满足条件的返回。 indexof用于判断字符串中是否存在该字符,存在则返回字符所在下标 }) } }
数组排序sort()
语法:
array.sort([函数])
数组的sort 函数用于数组中元素的比较。参数必须是一个函数,可选。
返回值:对数组的引用。
当该函数没有参数的时候默人按照字母的顺序进行排序,就是按照字符编码表上的顺序进行排序
如果需要自定义排序规则,需要使用函数作为参数,作为参数的函数需要返回一个用于说明这两个值相对顺序的数字。
比较函数有两个参数。a和b
如果a<b 在排序之后的数组中,a出现在b之前,返回一个小于0的值,从小到大
如果a>b ,在排序之后的数组中,a出现在b之后,返回一个大于0的值 从大到小
如果a=b ,则返回0
列表排序
要求:需要将用户按照姓名进行排序
通过计算属性实现
思路:
定义三个按钮用来表示升序,降序, 原顺序
使用三个数字表示三个顺序
0 原顺序 1 表示降序 2表示升序
三个按钮绑定一个属性,点击按钮的时候修改属性的值。
在计算属性中定义一个数组。
在搜索的基础上进行排序操作。通过数组的filter方法对数组中的元素进行过滤,在计算属性中返回执行进行条件判断
sortType的值是什么,通过sort函数使用年龄排序。该函数会操作原数组,将被排序之后的数组返回。
<body> <div id="app"> <h1>人员列表</h1> <input type="text" placeholder="请输入名字" v-model="keyword"> <button @click="sortType=2">年龄升序</button> <button @click="sortType=0">原顺序</button> <button @click="sortType=1">年龄降序</button> <ul> <li v-for="(per,index) of filPersons" :key="index"> { {per.sex}}----{ {per.name}}---{ {per.age}} </li> </ul> </div> <script> // 使用componed实现模糊查询 new Vue({ el: '#app', data: { name: '湖南职业学院', sortType: 0, //原顺序0 , 升序2 降序1 keyword: '', persons: [ { id: '001', name: '马冬梅', age: 23, sex: '女' }, { id: '002', name: '周冬雨', age: 21, sex: '女' }, { id: '003', name: '周杰伦', age: 33, sex: '男' }, { id: '004', name: '温兆伦', age: 32, sex: '男' } ] }, computed: { filPersons() { const arr = this.persons.filter((p) => { return p.name.indexOf(this.keyword) !== -1 }) // 判断一下是否是需要排序 if (this.sortType) { arr.sort((p1, p2) => { return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age }) } return arr } } }); </script> </body>
Vue数据监测原理
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script> let data = { name: '尚硅谷', address: '北京' } // 创建一个坚实的实例对象,用于监视data中属性的变化 const obs = new Observer(data); console.log(obs) // 准备一个vue实例 let vm = {} vm._data = data = obs; /* 创建一个构造函数,传递一个对象作为参数 将data作为参数传递到Observer构造器中。这个时候通过Observer构造函数实例化出来的对象中就包含了data中所有的数据 在构造函数中将data中的的属性名全部抽取出来作为一个数组存储。 然后遍历该数组,给数组中的每一个元素都进行一个函数执行。在每一次函数执行的时候使用defineProperty()方法处理正在被遍历的元素, 编写get set 方法,这个时候this是Observer实例对象,因为实例对象中有data中所有的数据,所以相当于操作了data。 最后将该实例对象重新赋值给data,这个时候原来的data就变成了obs,创建一个vm 对象将obj和data赋值给 _data */ // 能够创建一个监视的实例对象 function Observer(obj) { //汇总对象中所有的属性形成一个数组 const keys = Object.keys(obj); // 遍历 k表示的是遍历的keys中的每一个元素 keys.forEach((k) => { console.log(k) Object.defineProperty(this, k, { // 这里的this指的是Observer的实例对象 get() { return obj[k] }, set(val) { console.log("${k}被修改了,我要去解析模板了") obj[k] = val } }) }) } </script> </body> </html>
数组的forEach方法
forEach() 方法对数组的每个元素执行一次提供的函数。
var array = ['a', 'b', 'c']; array.forEach(function(a,b,c) { console.log(a); }); 使用箭头函数 array.forEach((ele,index,arr)=>{ }) forEach方法中的function回调有三个参数: a:第一个参数是遍历的数组内容, b:第二个参数是对应的数组索引, c:第三个参数是数组本身
Vue.set方法
向一个已经定义好的对象中在不修改源代码的情况下添加新的属性。
也可以对数组中的元素进行修改操作
语法:
Vue.set( target, propertyName/index, value )
Vue.set(vm.student.hobby,1,'吃饭') 将hobby数组中的 下标为 1 的元素进行修改为吃饭
参数:
{Object | Array} target
{string | number} propertyName/index
{any} value
向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如
this.myObject.newProperty = 'hi'
)注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。
用于向已经定义好的对象中添加属性,并且赋值。
语法:
Vue.set(target,key,val)
target:追加到哪里,哪一个对象
key:追加的属性名
val:追加的属性值
Vue.set(vm._data.student,'sex','男')
简写:
Vue.set(vm.student,'sex','男')
因为存在数据代理,_data中的数据vm都能读取到,所以直接使用vm中的student就可以操作vm._data.student
vm.$set方法
该方法和Vue.set方法作用一样,都是对象中添加属性。并且能被vue重新识别解析。
语法:
vm.$set(target,key,val)
vm.$set(vm._data.student,'sex','女')
缺点:
不能直接向Vue实例中添加数据,也不能向vm._data中添加数据
Vue监测数组
Vue没有通过get 和 set 进行监测数组中元素的修改。也就是说我们通过
vm.student.hobby[0]='打球'
Vue是不能监测到我们将数组中的元素修改了的。
只有通过调用了数组中的方法(数组中影响原数组的方法)进行调用
push :向数组最后添加一个元素
pop :向数组末尾删除一个元素
shift:删除数组中第一个元素
unshift:向数组第一个位置添加元素
splice :替换元素
sort :排序
reverse :颠倒
vm._data.student.hobby.push('学习') 向数组中添加学习
vm._data.student.hobby.shift() 删除第一个元素
vm._data.student.hobby.splice(0,1,'打球') 替换下标为0的元素,1表示删除1个元素,替换为打球
Vue是如何知道我们使用了数组中的方法呢?
Vue将被侦听的数组的变更方法进行了包裹,所以他们也将会触发视图更新,这些被包裹的方法包括了:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
也就是Vue将上述方法进行了重写,在Vue重写的方法中,调用了原始Js中的Array原型对象中的原始方法,并增强了Vue独特的部分,所以我们通过数组调用的push之类的方法并不是Array原型中的,而是Vue自身提供的。
数组的splice()函数
splice(index ,howmany , item1, …, itemX )
参数:
index —— 必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。
howmany —— 必需。要删除的项目数量。如果设置为 0,则不会删除项目。
item1, …, itemX —— 可选。向数组添加的新项目。
index >0 时 (1. howmany 为 0 时 不删除只添加 —— 在index位置前添加item1, …, itemX的数 (2. howmany > 0 删除且添加 —— 在index位置前添加item1, …, itemX的数,并且删除从index位置开始的数,howmany为删除的个数 index <0 时 最后一个数为 -1 依次倒数第二个数为-2 (1. howmany 为 0 时 不删除只添加 —— 在-index位置前添加item1, …, itemX的数 (2. howmany > 0 删除且添加 —— 在-index位置前添加item1, …, itemX的数,并且删除从-index位置开始的数,howmany为删除的个数
关于Vue数据监测总结:
1.Vue会监视data中所有层次的数据
2.如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监视的数据
1)对象中后追加的属性,Vue模版不做响应式处理
2) 如需后添加的属性做响应式处理,使用API
Vue.set(target , propertyName/index , value) 或者
vm.$set(target ,propertyName/index,value)
3.如何监测数组中的数据
通过包裹数组更新元素的方法实现,本质就是两件事:
调用原生对应的方法对数组进行更新
2.重新解析模板,进而更新页面
4.在Vue修改数组中的某个元素时一定要使用
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
这些方法对数组进行更新操作,如果需要使用filter(),因为filter()函数是不会影响原数组的,所以需要将filter方法的数组重新赋值给原数组。
或者使用Vue.set()或者 vm.$set()
注意:Vue.set() 和vm.$set() 不能给vm或者vm的根数据对象 添加属性。
数据劫持:
数据劫持,指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。
我们data中的数据和vm中的_data是不同的,这个就是数据劫持。
Vue收集表单信息
多选框:
当我们需要收集表单信息的时候,使用v-model和属性进行绑定。要求input框必须存在value值,如果不存在value值所绑定的就是是否勾选。收集数据必须是一个数组类型。
如果我们使用多选框是用于判断是否勾选的时候可以使用字符串进行接收数据。
对象转换成JSON
JSON.stringify(对象)
v-model的修饰符
v-model.number : 将输入的值转换成number类型,通常和type='number' 一起使用。
v-mode.lazy:当失去焦点的时候进行数据的收集操作,不再是实时收集。比如我们输入框每变化一次就刷新一次,过于频繁。
v-mode.trim:去除前后的空格,不收集前后的空格。
总结:
收集表单数据:
<input type='text' / >收集的是value的值
<input type="radio" /> 收集的是value 值 需要自己手动添加value
<input type="checkbox" /> 没有配置value值收集的是checked是否勾选,返回boolean值
配置了value值:
v-model的初始值是非数组,那么收集到的就是checked是否勾选的boolean值
初始值是数组,那么收集的就是value值组成的数组
备注:v-model三个修饰符
lazy:失去焦点后收集数据
number:输入的字符串转换成数字,非数字字符串不收集
trim:输入首位空格
过滤器
用于插值语法和v-bind中的简单逻辑处理
定义:
对要显示的数据进行简单恶特定格式化后再显示,适用于一些简单的逻辑处理
语法:
1.注册过滤器 Vue.filter(name,callback) 或者 new Vue(filters:{})
使用过滤器{ {xxx| 过滤器名}} 成 v-bind:属性= “xxx|过滤器名”,xxx始终会作为第一个参数传入过滤器中
备注:
1.过滤器也可以接受额外的参数,多个过滤器可以串联
并没有改变原本的数据,是产生新的对应的数据。
Vue.filter("过滤器名",function(){}):这个方法配置的是全局的过滤器,需要在new Vue实例之前进行声明
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../js/vue.js"></script> <script src="../js/dayjs.min.js"></script> </head> <body> <div id="app"> <h2>显示格式化之后的时间</h2> <!-- 计算属性实现 --> <h3>现在是:{ {fmtTime}}</h3> <!-- methods实现 --> <h3>现在是: { {getFmtTime()}}</h3> <!-- 过滤器实现 time是我们需要处理的数据,timeformater是过滤器, Vue会将time传入timeformater中,将返回值替换调所有的{ {}}内容--> <h3>现在是:{ {time | timeformater}}</h3> <h3>现在是:{ {time | timeformater('YYYY_MM_DD')}}</h3> <!-- 过滤器可以叠加 time首先会交给timeformater进行处理,处理结束之后的返回值再次交给mySlice进行处理--> <h3>现在是:{ {time | timeformater('YYYY_MM_DD') | mySlice}}</h3> <!-- 第二种用法:截取标签中属性值的部分:截取msg中前四个元素 --> <h2 :x="msg | mySlice">你好</h2> </div> <div id="app2"> { {msg | mySlice}} </div> <script> // 全局过滤器 Vue.filter('mySlice', function (value) { return value.slice(0, 4) }) new Vue({ el: '#app', data: { time: Date.now(), msg: '你好世界你好世界' }, methods: { getFmtTime() { return dayjs(this.time).format('YYYY年MM月DD日 HH时mm分ss秒') } }, computed: { fmtTime() { //dayjs中如果不传入参数就是当前时间 return dayjs().format('YYYY年MM月DD日 HH时mm分ss秒'); } }, /** 过滤器的工作流程: { {time | timeformater}} timeformater是一个过滤器,time将作为参数传入该过滤器,不需要写()同样可以传入到过滤器中。由Vue为我们调用 返回结果将整个替换掉{ {}} 里面的内容。 当timeformater过滤器中存在参数的时候,time同样会作为第一参数传递到过滤器中,自己传入的参数会作为第二参数传递进去。 */ // 局部过滤器,只能在该vue实例中使用 filters: { // 过滤器的本质是一个函数,给str赋默认值,当模板中过滤器传入参数的时候使用模板中的参数,如果没有传递参数使用str默认的参数 timeformater: function (val, str = 'YYYY年MM月DD日 HH:mm:ss') { //过滤器能对我们的数据进行加工 console.log('@', val); return dayjs(val).format(str); }, mySlice(val) { return val.slice(0, 4); // 截取 0 -4 之间的元素 } } }); new Vue({ el: '#app2', data: { msg: 'hello world' } }); </script> </body> </html>
Vue中内置指令
v-text指令
作用:向其所在的节点中渲染文本内容
与插值语法的区别:v-text会替换掉节点中的内容,{ {xx}}不会
v-html指令
作用:向指定的节点中渲染包含html结构的内容
与插值语法区别:
1.v-html会替换掉节点中所有的内容,{ {xx}}不会
2.v-html可以是被html结构
严重注意:
1.在网站上动态渲染任意html是非常危险的,容易导致xss攻击
2.一定要在可信的内容上使用v-html。永远不要相信用户提交的内容。document.cookie
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../js/vue.js"></script> </head> <body> <div id="app"> <div>{ {name}}</div> <div v-html="str"></div> <div v-html="str2"></div> </div> <script> new Vue({ el: '#app', data: { name: '湖南职业学院', str: '<h3>你好呀</h3>', str2: '<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟点我试试</a>' } }); </script> </body> </html>
v-cloak指令
当发生js阻塞的时候配合css实现页面延迟展示
1.本质是一个特殊属性,Vue实例创建完成并接管容器之后,会控制删除v-cloak属性
2.使用css配合cv-cloak可以解决网速较慢的识货页面展示{ {name}}问题。
作用:该标签在Vue介入的时候就被Vue删除。
可以在网速过慢的时候,让未经过vue解析的模板不显示在页面中。
<style> [v-cloak] { display: none; } </style> </head> <body> <div id="app"> <div v-cloak>{ {name}}</div> </div> //Vue在这里加载,上面的模板会首先解析,并显示在页面中,但是因为没有加载Vue,所以展示的是{ {name}}效果。这不是我们想让用户看到的 <script src="../js/vue.js"></script> <script> new Vue({ el: '#app', data: { name: '湖南职业学院' } }); </script>
v-once指令
没有属性值。
作用:
1.v-once节点在初次动态渲染之后就会视为静态内容,也就是只会渲染一次,不管{ {}}里面的值如何变化,都不会再发生改变
2.以后数据的改变不会引起v-once所在结构的更新。可以用于优化性能。
也就是说使用了该指令,只会解析一次,之后就不会再解析了。
<body> <div id="app"> <h1 v-once> n的初始值:{ {name}}</h1> <h1>{ {name}}</h1> <button @click="name++">n++</button> </div> <script> new Vue({ el: '#app', data: { name: 1 } }); </script> </body>
v-pre指令
作用:跳过其所在节点的编译过程
可以利用它跳过:没有使用指令语法,没有使用插值语法的节点,会加快编译。
Vue在解析模板的时候节点不管是否存在vue指令或者插值语法,都会被Vue编译,就造成了很多不必要的编译过程。该指令可以跳过这些不必要的元素节点。
<div id="app"> <h1 v-pre>很完美</h1> <h1 v-pre>{ {name}}</h1> <button @click="name++">n++</button> </div> 使用了v-pre指令不会被编译
自定义指令
什么是自定义指令:
指令:用来操作DOM的操作,相当于封装好的函数,只要和对应的元素进行绑定就可以实现不同的功能
自定义指令:除了内置指令以外的我们自己定义的指令。用于操作DOM的。自定义指令可以方便的实现和扩展,不仅可以用于定义任何DOM操作,而且是可复用的。
定义指令的时候不要加v-
使用指令的时候加上v-
自定义指令的两种形式
函数形式:
使用方式:
<span v-big="n"></span>
语法:
directives:{
指令名(element,binding){
}
}
directives和Data平级,是Vue实例中的一个配置项,所有的自定义指令都需要在directives中进行配置。
指令名:是自定义指令的名字。不需要v-开头,但是在元素中使用指令的时候需要使用v-开头‘
element:就是使用了该指令的元素,该指令放在哪个标签中,这个element就是谁
binding:元素和标签的绑定关系。里面存着元素和标签之间信息。是一个对象,里面的value属性存储的是自定义指令所使用到的属性。
执行条件:
1.指令和元素成功绑定的时被调用
2.指令所在的模板被重新解析时
缺点:因为指令和元素成功绑定的时候就会被调用,但是这个时候元素还没有展示在页面中,一些需要展示在页面中才能进行的操作就不会有作用。
对象形式:
对象形式是自定义指令的完整形式。
语法:
指令名:{
bind(element,binding){
//指令和元素成功绑定时调用
}
inserted(element,binding){ //指令所在元素被插入页面的时候
}
update(element,binding){ //指令所在的模板被重新解析时
}
}
directives: { fbind: { // 函数名是固定的,固定的函数Vue会在指定的时间对其进行调用,所以不能随便更改 bind(element, binding) { // 指令和元素成功绑定时调用 // console.log("bind") element.value = binding.value }, inserted(element, binding) { //指令所在元素被插入页面的时候 // console.log("inserted") element.focus()//自动获取焦点,无效 }, update(element, binding) { //指令所在的模板被重新解析时 console.log("update") element.value = binding.value // element.focus() } } }函数式相当于只有bind和update两个操作,没有inserted操作
语法:
directives: { // big函數什么时候被调用: /* 1.指令和元素成功绑定的时被调用 2.指令所在的模板被重新解析时。 */ big(element, binding) { /* element:是一个真实DOM binding:表示绑定,是指元素和指令之间的关联关系 */ // console.dir(element) 验证element是真实的DOM // console.log(element instanceof HTMLElement) console.info(binding) element.innerText = binding.value * 10 } } });
当我们使用函数作为一个指令的时候:该函数执行的条件是:
-
指令和元素成功绑定的时候被调用
-
指令所在的模板被重新解析的时候
自定义全局指令
全局指令就是在全部的vue实例中都能使用的指令。使用在<script>标签中
语法:
Vue.directive(id,{})
id:指令的名字
{}:配置项
//对象式定义 Vue.directive('big', { bind(element, binding) { element.innerHTML = binding.value * 10 }, update(element, binding) { element.innerHTML = binding.value * 10 } }) //函数式定义 Vue.directive('big',function(element,binding){ xxx })
自定义局部指令
自定义指令要求
命名规范:
多个单词拼接的的指令名,单词之间使用-进行分隔。
在directives配置项中定义的时候需要使用''进行包裹,js中属性名的完整写法。
'big-number' 定义 因为属性名中存在-这种特殊字符 v-big-number使用
<h1 v-big-number="name"></h1> directives:{ 'big-number'(){ } }
指令的this
指令中的回调函数中的this是window
bind(){
console.log(this)//这里的this是window
}
指令总结:
配置对象中常用的三个回调
1).bind 指令与元素陈宫绑定时调用
2).inserted :指令所在元素被插入页面时调用
3)update:指令所在模板结构被重新解析时调用
备注:
指令定义时不加v-,使用时加v-
指令名如果是多个单词,要使用kebab-case命名方式,不能使用驼峰
Vue生命周期
生命周期:
1.又名:生命周期函数,生命周期回调函数,生命周期钩子
2.是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数
3.生命周期函数的名字不可更改,但是函数的具体内容由程序员根据需求编写
4.生命周期函数中的this指向的是vm,或者是组件实例对象
<body> <div id="app"> <h2 :style="{opacity}">欢迎学习Vue</h2> </div> <script> new Vue({ el: '#app', data: { name: '湖南职业学院', opacity: 1 }, // Vue完成模板的解析并把这是DOM元素放到页面后(挂载完毕)调用mounted mounted() { console.log('我是挂在函数') setInterval(() => { this.opacity -= 0.01 if (this.opacity <= 0) { this.opacity = 1 } }, 16); } }); </script> </body>
生命周期图解:
使用template作为vue模板的时候,不能使用<templeate>作为根元素,可以使用在模板里面。
vm生命周期
将要创建----调用beforeCreate函数
创建完毕----调用created函数
将要挂载----调用beforeMount函数
挂载完毕----调用mounted函数=====》重要的钩子
这里做初始化操作。将要更新----调用beforeUpdate函数---在这个时候界面和数据不保持一致。
更新挖鼻----滴啊用updated函数
将要销毁----调用beforeDestroy函数==》重要钩子,完全销毁一个实例,清理它与其他实例的连接,解绑它的全部指令以及事件监听器。
销毁的是自定义事件,这个阶段不能操作数据,即使操作了数据也不会被数据也不会被修改。
销毁完毕----调用destroyed函数
常用的生命周期钩子:
1.mounted:发送ajax请求,启动定时器,绑定自定义事件,订阅消息等(初始化操作)
2.beforeDestroy:清除定时器,解绑自定义事件,取消订阅消息等(收尾工作)
关于销毁Vue实例
1.销毁后借助Vue开发者工看不到任何信息
2.销毁后自定义事件会失效,但是原生的DOM事件依然有效
3.一般不会再beforeDestroy操作数据,因为即使操作了数据,也不会再出发更新流程。
组件
组件的定义:
实现应用中局部功能代码和资源的集合。就是将页面分为几个部分,每一个部分都是一个单独的完整的结构,包括css,js,html。
代码:css,js,html 资源:mp3 ,mp4
就是为了实现代码复用,对部分代码块和资源实现了局部封装。
模块与组件,模块化和组件化
模块:
理解:向外提供特定功能的js程序,一般就是一个js文件
为什么:js中有很多很复杂的代码,可以重用
作用:复用js简化js的编写,提高js运行效率
组件:
理解:用来实现局部(特定)功能效果的代码集合
为什么:一个界面的功能很复杂
作用:复用编码,简化项目编码,提高运行效率。
模块化:
当应用中的js都是以模块来编写的 ,那么这个应用就是一个模块化的应用
组件化:
当应用中的功能都是多组件的方式来编写的 ,那么这个应用就是一个组件化的应用。
组件化编程
非单文件组件:
一个文件中包含有多个组件。
单文件组件:
一个文件中只包含一个组件。a.vue
属性的简写形式
如果对象字面量的属性由外部变量引入,且键名与变量名相同,值不变,则可以只写键名。
let s = 5 let obj={ s // 这里属性名和属性值得名字一致,并且这个属性值必须是已经定义好的变量,赋值形式传递属性 }
Vue中使用组件的三大步骤:
一、定义组件(创建组件)
const school = Vue.extend({ // html框架 template: ` <div> <h1>学校名称:{ {schoolName}}</h1> <h1>学校地址:{ {schoolAddress}}</h1> </div> `, data() { return { schoolName: '北京大学', schoolAddress: "北京" } } });二、注册组件
全局注册
全局注册是可以在多个容器中使用,也就是多个root容器
Vue.component("hello", hello) 组件名,组件变量局部注册
new Vue({ el: '#app', components: { // 第二步:注册组件(局部注册) //真实组件名:定义的组件中转变量 true_school: school, // 这是属性的简写形式,当属性名和引用的变量名一致的时候,可以直接写:student:student student } })三、使用组件(写组件标签)
<div id="app"> <!-- 编写组件标签 --> <true_school></true_school> <student></student> </div>
如何定义组件
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的配置项几乎一样,但是不能使用el
区别:
el:不能写,为什么,因为所有的组件都是要经过vue的管理,由vue中的el决定服务于哪一个容器,
data必须写为函数,避免了组件被复用,数据存在引用关系。如果data是一个对象,那就存在一个引用关系,a是用来这个组件中的data,b也使用了组件中的data,当a修改了data中的属性的时候,b的组件中的属性也被修改了。
组件中的data写的是一个函数,函数返回一个对象,这样子可以做到每一个组件中的对象都是唯一的。
备注:适应template可以配置组件结构。
如何注册组件:
1.局部注册:靠new Vue的时候传入components选项
2.全局注册:靠Vue.component('组件名',组件)
使用组件:
在容器中使用标签<hello></hello>
组件的命名方式
1.一个单词组成的:
第一种写法(纯小写):school
第二种写法(首字母大写):School
2.多个单词组成的:
第一种写法:(kebad-case命名):my-school
第二种写法:(CamelCase命名)MySchool (需要使用Vue脚手架)
备注:
组件名称尽可能的回避html中已经有的元素名,比如 h1 H2都不行
可以使用name配置项指令组件在开发者工具中呈现的名字
const student = Vue.extend({ name: 'hello', 。。。 }) 在Vue里面设置name可以指定在开发者工具中的名字
关于组件标签
第一种写法:<school></school>
第二种写法:<school/>
备注:不能使用脚手架时,<school/> 会导致后续组件不能渲染。
3.一个简写方式:
const school = Vue.extend(option) 可以简写为const school =options
const s = Vue.extend({}) => const s ={} new Vue({ 。。。 components:{ school:s // 如果没写Vue.extend()在这里会执行一次Vue.extend({}) } })
组件案例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../js/vue.js"></script> </head> <body> <div id="app"> <hello></hello> { {msg}} <!-- 编写组件标签 --> <school></school> <hr /> <student></student> </div> <div id="app2"> <hello></hello> </div> <script> // 创建school组件构造器(extend:扩展) const school = Vue.extend({ template: ` <div> <h2>学校名称:{ {schoolName}}</h2> <h2>学校地址:{ {address}}</h2> <button @click="showName">点我提示学校名</button> </div> `, // el: '#app', 组件不能使用el,组件不指定为谁服务 data() { // data必须写成一个函数的形式返回。 return { schoolName: '湖南科技', address: '天心区' } }, methods: { showName() { console.log(this) alert(this.schoolName) } } }) // 创建student组件 const student = Vue.extend({ template: ` <div> <h2>学生名称:{ {studentName}}</h2> <h2>学生年龄:{ {age}}</h2> </div> `, data() { return { studentName: '那年', age: 18 } } }) // 创建组件hello const hello = Vue.extend({ template: ` <div> <h1>你好! { {name}}</h1> </div> `, data() { return { name: 'Tom' } } }) // 定义全局组件 Vue.component('hello', hello) // 创建Vue1 new Vue({ el: '#app', // 祖册组件,局部注册 components: { //组件 school, // 组件名:组件中转变量 只写组件名,简写形式,当属性名和被引用的属性值名字一样的时候 student }, data: { msg: '你好' } }) // 创建vue实例2 new Vue({ el: '#app2' }) </script> </body> </html>
组件简写
<script> // 创建学校组件 const s = { name: 'nanian',//使用name配置项指令组件在开发者工具中呈现的名字 template: ` <div> <h2>学校名称:{ {name}}</h2> <h2>学校地址:{ {address}}</h2> </div> `, data() { return { name: '湖南科技', address: '天心区' } } }; new Vue({ el: '#app', data: { msg: '欢迎学习' }, components: { school: s } }) </script>
组件嵌套
因为组件除了不能使用el以及data必须是一个函数以外,其他的都和Vue实例一样,所以组件中也可以有components属性,用来指定组件为谁服务。当组件中使用components,那么该组件就是components中被包裹的组件的父组件。
注意:子组件要定义在父组件前面。因为我们要在父组件中引入子组件,要先定义在引用。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../js/vue.js"></script> </head> <body> <div id="app"> </div> <script> // 定义组件 const student = { template: ` <div> <h2>学生名称:{ {name}}</h2> <h2>年龄:{ {age}}</h2> </div> `, data() { return { name: 'make', age: 18 } } }; // 创建学校组件 const school = { // name: 'nanian', template: ` <div> <h2>学校名称:{ {name}}</h2> <h2>学校地址:{ {address}}</h2> <student></student> </div> `, // 局部注册组件 components: { student }, data() { return { name: '湖南科技', address: '天心区' } } }; const hello = { template: ` <div> <h1>hello 欢迎学习Vue</h1> </div> ` } const app = { template: ` <div> <h1>hello 欢迎学习Vue</h1> <school></school> <hello></hello> </div> `, components: { school, hello } } new Vue({ template: '<app></app>', el: '#app', // 注册局部组件 components: { app } }) </script> </body> </html>
VueComponent
关于VueComponent:
const school = Vue.extend({})
school 组件的本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
我们只需要写<school></school> Vue解析的时候就会帮我们创建school组件的实例对象。
即Vue帮我们执行了:new VueComponent(option)
注意:每次调用Vue.extend,返回的都是一个全新的VueComponent
关于this指向:
组件配置中 data函数,method中的函数,watch中的函数,computed中的函数,他们的this都是VueComponent实例对象
new Vue()配置中:
data函数 methods中的函数 ,watch中的函数 ,computed中的函数 他们的this都是Vue实例对象
VueComponent实例对象 简称vc ,也可以称为组件实例对象
Vue的实例对象,以后简称vm
vc和vm的关系
组件是可复用的vue实例,像data,computed,watch,methods以及生命周期钩子等,仅有的例外是像el这些跟实力特有的选项
组件内置关系
显示原型属性
const d = new Demo(); Demo.prototype函数身上的是显示原型属性
隐示原型属性
const d = new Demo(); d.__proto__实例身上的是隐示原型属性。
特点
不管是显示原型属性还是隐示原型属性都指向了同一个原型对象。
一般通过显示原型对象传递数据,通过隐示原型对象获取到数据。
<script> function Demo() { this.a = 1 this.b = 2 } const d = new Demo(); console.log(Demo.prototype); // 显示原型属性 console.log(d.__proto__) // 隐示原型属性 console.log(Demo.prototype == d.__proto__) //true Demo.prototype.x = 10; console.log(d.__proto__.x); //10 console.log(d.x); // 10 如果本地没有就会去原型对象上找 console.log("@", d) //@ Demo {a: 1, b: 2} </script>
在Vue里面也有一个关于原型的内置关系
一个重要的内置关系:VueComponent.prototype.__proto__ = = = Vue.prototype
为什么要有这个关系:让组件实例对象(vc)可以访问到Vue原型上的属性,方法。
Vue原型上有相当多的方法,如果没有黄色的线vc就不能使用vm原型中的方法。
单文件组件
什么是单文件组件,就是一个文件表示一个组件。文件名以vue结尾。注意:vue文件的文件名命名规范和组件的命名规范一致。
在文件中有三个标签:分别表示结构,交互 ,样式
script中的交互内容需要进行暴露,外部才能引用。暴露有三种方式,分别暴露,统一暴露,默认暴露
<template> //结构 </template> <script> //交互 </script> <style> //样式 </style>
定义一个app.vue用来管理其他的组件,app.vue不需要自己的数据,只需要将其他的组件作为自己的子组件引入
<template> <div> <School></School> <Student></Student> </div> </template> <script> //引入组件 import School from "./School.vue"; import Student from "./Student"; export default { name: "App", components: { School, Student, }, }; </script> <style> </style>
js暴露方式:分别暴露,统一暴露,默认暴露
分别暴露 export const school = Vue.extend({ data() { return {}; }, }); 统一暴露 const school = Vue.extend({ data() { return {}; }, }); export{school} 默认暴露 const school = Vue.extend({ data() { return {}; }, }); export default school; 简写方式 export default {}; ==》export default Vue.extend({})
定义一个main.js,在该js文件中定义vue实例,用来管理所有的组件,只需要引入App.vue一个组件就可以实现所有组件的管理。
import App from './App.vue' new Vue({ el: '#app', template: '<App></App>', components: { App } })
最后是index.html ,表示容器,在容器中引入main.js以及vue.js
<body> <div id="app"> </div> <script src="../../js/vue.js"></script> <script src="./main.js"></script> </body> </html>
使用脚手架
什么是脚手架?(cli:command line interface )命令行接口工具
Vue脚手架时Vue官方提供的标准化开发工具(开发平台)。
脚手架官网:Vue CLI
具体步骤:
第一步:全局安装@vue/cli
测试是否安装成功:ctrl+r进入命令行窗口,输入vue。
npm install -g @vue/cli
第二步:切换到你好创建项目的目录,然后使用命令创建项目
vue create 项目名
输入
vue create xxx
之后会进入到下面的选项,选择vue的版本
选择需要的版本之后回车。创建完成之后会展示下面的界面。
第三步:启动项目
npm run serve
运行结果:
备注:
将npm下载的地址配置成淘宝的镜像地址。使用淘宝镜像下载:
npm config set registry https://registry.npm.taobao.org
脚手架生成的项目结构
主页面文件中的<%= BASE_URL%> 表示是public文件夹
vue文件最终会转换成js,css,html文件
render函数
因为我们默认引入的vue是一个精简版的vue,不包含vue模板编译器。因为我在开发的时候不需要模板解析器。
render函数可以让我们编译模板。
语法:
render(createElement) { return createElement('h1','你好') } 该函数必须存在返回值,不然无法通过编译。 精简版:ES6语法 render: createElement => createElement('h1', '你好'),关于不同版本的Vue
1.Vue.js与vue.runtime.xxx.js的区别
1)vue.js是完整版的Vue,包含了核心功能+模块解析器
2)vue.rentime.xxx.js 是运行版Vue,只包含核心功能,没有模板解析器
2.因为Vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接受到的createElement函数指定具体内容。
修改默认配置
默认情况下Vue会将所有的默认配置进行隐藏
使用命令:vue inspect > output.js
可以将所有的默认配置以js的形式输出展示。这个js只是展示并不支持修改。
vue.config.js文件中可以对默认的配置进行配置修改,帮助地址:配置参考 | Vue CLI
module.exports = { pages: { index: { // page 的入口 entry: 'src/index/main.js', // 模板来源 template: 'public/index.html', // 在 dist/index.html 的输出 filename: 'index.html', // 当使用 title 选项时, // template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title> title: 'Index Page', // 在这个页面中包含的块,默认情况下会包含 // 提取出来的通用 chunk 和 vendor chunk。 chunks: ['chunk-vendors', 'chunk-common', 'index'] }, // 当使用只有入口的字符串格式时, // 模板会被推导为 `public/subpage.html` // 并且如果找不到的话,就回退到 `public/index.html`。 // 输出文件名会被推导为 `subpage.html`。 subpage: 'src/subpage/main.js' } }
关闭语法检查
同样是在配置文件中进行修改
module.exports = { pages: { index: { // page 的入口 entry: 'src/main.js', } }, lintOnSave: false }
ref属性
被用来给元素或者子组件注册引用信息(id替代者)
应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象
使用方式:
打标识<h1 ref="xxx">...</h1> 或者<School ref="xxx"></School>
获取:this.$refs.xxx
<template> <div> <h1 v-text="msg" ref="title"></h1> <!-- 点我输出 --> <button @click="show" ref="btn">点我输出</button> <School ref="sch" id="sch" /> </div> </template> <script> // 引入school import School from "./components/School"; export default { name: "App", components: { School }, data() { return { msg: "你好", }; }, methods: { show() { console.log(this.$refs.title); //真实DOM元素 console.log(this.$refs.btn); //真实DOM元素 console.log(this.$refs.sch); //vc vuecomponent实例对象 console.log(document.getElementById("sch")); //通过id获取到的是该组件原生的html结构 console.log(this.$refs); }, }, }; </script> <style> </style>
id和ref的区别
如果给组件标签绑定一个id属性,那么最终这个id会绑定到组件所对应的html结构上
<School id="hello"/>
如果给组件标签绑定一个ref属性,得到的是一个组件实例对象,这个组件对象是School组件实例对象
<School ref="hello" />
模板间数据传递
props属性
父传子
该属性可以用来接收父模板传递过来的数据。
功能:
让组件接收外部传递进来的数据
1)传递数据:
语法:<demo name="" />
<Student name="李四" sex="女" :age="18" /> // 这里的:是v-bind,动态绑定,双引号里面的将不再是字符串,而是一个表达式 name sex age 这些是属性名 ,Student是模板标签2)接收数据:
语法:第一种方式(只接收):
props:['name','sex']
props: ["name", "age", "sex"],// 简单声明接受 []里面的对应的是传递过来的属性名第二种方式:(限制类型)
props:{name:Number}
props:{属性名:限制的类型} props:{ name:String }第三种方式:(限制类型,指定默认值,指定必要性)
props:{ 属性名:{type:类型,required:必要性,default:默认值}}
props: { name: { type: String, //name的类型是字符串 required: true, // name是必须的 }, age: { type: Number, default: 99, }, sex: { type: String, required: true, }, },注意:props只读,Vue底层会检测你对props的修改,如果进行了修改就会发出警告,不要修改props接收到的数据。
如果需要对传递进来的数据进行修改,因为组件解析方式是先解析props中的属性,再解析data配置项中的属性,这个时候可以在data中定义一个属性接收props传递进来的属性。
data:{ myName:this.name name:'zhangsan' //这是不被允许的,因为props中已经存在了name属性,会出现错误,会优先解析并展示props中的 } props:['name','sex'] //这里的会被先解析,然后在解析data中的数据
props属性默认是单向绑定,.sync实现显式的双向绑定
<child :my-name.sync="nanian"> </child>
单次绑定
<child :my-name.once="nanian"> </child>
mixin混入(混合)
组件之间使用同一个配置
功能:可以把多个组件共用的配置提取成一个混入对象
使用方式:
第一个步:定义混合,例如
{ data(){。。。} methods:{} }第二步:使用混入
全局混入:Vue.mixin(xxx)
局部混入:mixins:['xxx']
混入方式:
局部混入:
定义混入
export const mixins = { methods: { showName() { alert(this.name); }, }, } export const mixins1 = { data() { return { x: 100, y: 200 } } }使用混入
<script> import { mixins, mixins1 } from "../mixin"; //引入混入 mixins: [mixins, mixins1], //使用混合 </script>
全局混入,在main.js中
import Vue from 'vue' import App from './App.vue' // 关闭vue的生产提示 Vue.config.productionTip = false import { mixins, mixins1 } from './mixin' Vue.mixin(mixins) Vue.mixin(mixins1) new Vue({ el: '#app', render: h => h(App) })
Vue插件
本质是一个对象
功能:用于增强Vue
本质:包含了install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
定义插件:
export default { install(Vue,参数1,参数2) { // 定义过滤器 Vue.filter() // 定义指令 Vue.directive() // 配置全局混入 Vue.mixin() // 添加实例方法 Vue.prototype.$method = function (options) { }; Vue.prototype.$myProperty = xxx } }使用插件
Vue.use(插件名,参数1,参数2..)
scoped样式
当多个组件中有同名的元素情况,当我们使用样式的时候就会发生冲突,因为不同的组件之间可以存在同class的元素,并且组件的样式是自己的。
使用方式:
<style scoped> //使用了scoped该样式就能解决样式冲突 .demo { background-color: orange; } </style>
原理:
他会自动给我们的组件的根标签上加一个特殊的标签属性,随机生成的会有唯一性。在使用样式的时候使用类名加标签属性的形式实现唯一性确定。但是App组件不适用。
slot插槽
我们可以在组件标签之间写自定义的代码对组件进行扩展。比如:
<组件名><img src="test.jsp"/></组件名> ........组件内部....... <div> <h1>我是组件内部</h1> <slot>组件标签之间的内容就显示在这个位置</slot> </div>我们需要告诉使用vue我们将这个扩展的内容放到组件的什么位置,因为我们知道组件的内部可以是很多的html代码。
使用<slot>标签就可以告诉Vue扩展的部分放在什么位置。模板标签中的扩展内容的样式可以放到app中或者组件中都可以。
实现组件功能的扩展。
默认情况下当我们使用了组件标签的时候,是无法在组件标签中再写其他的内容的。
<zidingyi>自定义内容</zidingyi> //这里面的自定义内容是不显示的。
单slot插槽(默认插槽)
当模板中只有一个没有属性的slot标签时,会将自定义内容替换掉<slot></slot>,并展示在页面中
const zujian ={template:'<div><h1>你好我是张三</h1> <slot>slot插槽</slot> </div>'} <zujian><h2>你好呀</h2></zujian> h2标签会替换掉包括<slot>内的模板中的所有内容,包括<slot>标签
具名slot插槽
当自定义组件标签中存在多个其他的元素,可以使用具名slot插槽指定将这些不同的元素放到模板中什么样的位置。
<div id="app"> <t> <h1 slot="header">你好</h1> 指定slot名字,将其放到模板的什么地方这个h1放到header插槽中 <h1 slot="body">我怕黑</h1> <h1>你呢</h1> </t> </div> <script> // 定义第一个组件 const t = { template: '<h1>你好我<slot name="header"></slot>是张三,我想你了你知道吗美女认识一下好吗<slot name="body"></slot></h1>' } </script>
Vue2.6新特性:
在使用<template>标签进行结构包裹的时候,可以使用
v-slot:插槽名
的形式指定结构放哪个插槽中<template v-slot:footer> <h1>xxx</h1> </template> //表示将该结构放到footer插槽中
slot作用域插槽
什么是作用域插槽?
当我们的数据在子组件中,但是父组件需要使用到该数据。并且需要在组件中进行展示。
从字面意思来说,作用域就是数据的作用域,需要的数据在子组件中,使用组件的是父组件。这个时候就出现了作用域问题,我们需要的数据作用域只在子组件中。通过作用域插槽就可以将子组件的数据传递给父组件。父组件使用特殊的方式接收数据之后就可以之间使用。
作用域插槽也可以有名字
在父级中,具有特殊形式scope的template元素,表示它是作用域插槽的模板。scope的值对应的是一个临时变量,此变量接收从子组件中传递的prop对象。
作用域插槽是一种子向父传参的一种形式,在子组件模板中定义一个插槽,在容器中使用<template scope='myScope'>
<div id="app"> <t> <!--临时变量--> <template scope="myScope"> <span>这里是hello world,{ {myScope.text}}</span> </template> </t> </div> <script> // 定义第一个组件 const t = { template: '<div><slot text="hello world"></slot></div>' }
案例
子组件
从子组件中可以看到,我们需要的数据games是存储在子组件中的,<slot>标签是插槽,通过:games="games"的方式给该组件的使用者赋值。我们之前学过父组件给子组件传递数据<demo :test="test">。这里就是子组件的数据传递给组件的使用者,也就是父组件。<slot :games="games"></slot>
<template> <div class="category"> <h3>{ { title }}分类</h3> // 这里是将子组件的数据传递给父组件,传递给了插槽的使用者 <slot :games="games">我是默认的内容</slot> </div> </template> <script> export default { name: "Category", props: ["title"], data() { return { games: ["荣耀", "王者", "吃鸡", "使命"], }; }, }; </script>
父组件
父组件需要接收子组件传递过来的数据
接收方式:
定义<template>标签,必须使用该标签才能进行接收
在<template scope="临时变量”> 标签中使用 scope属性进行接收,接收到的值就存储在临时变量中。
<组件标签 title="游戏"> <template scope="hunakeji"> //定义变量接收子组件传递的数据 <ul> <li v-for="(item, index) in hunakeji.games" :key="index"> { { item }} </li> </ul> </template> </组件标签> <category title="游戏"> <template scope="hunakeji"> <!-- { { hunakeji.games }} --> <ul> <li v-for="(item, index) in hunakeji.games" :key="index"> { { item }} </li> </ul> </template> </category> //ES6解构赋值 <category title="游戏"> <!-- {games}:解构赋值方式:ES6对象或者数据的赋值方式,就是hunan.games --> <template scope="{games}"> <ol> <li v-for="(item, index) in games" :key="index"> { { item }} </li> </ol> </template> </category> //新的写法 <category title="游戏"> <template slot-scope="{ games }"> <h4 v-for="(g, index) in games" :key="index">{ { g }}</h4> </template> </category> </div> </template>
插槽总结
-
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件 。
-
分类:默认插槽、具名插槽、作用域插槽
-
使用方式:
-
默认插槽:
父组件中: <Category> <div>html结构1</div> </Category> 子组件中: <template> <div> <!-- 定义插槽 --> <slot>插槽默认内容...</slot> </div> </template>
-
具名插槽:
父组件中: <Category> <template slot="center"> <div>html结构1</div> </template> <template v-slot:footer> <div>html结构2</div> </template> </Category> 子组件中: <template> <div> <!-- 定义插槽 --> <slot name="center">插槽默认内容...</slot> <slot name="footer">插槽默认内容...</slot> </div> </template>
-
作用域插槽:
-
理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
-
具体编码:
父组件中: <Category> <template scope="scopeData"> <!-- 生成的是ul列表 --> <ul> <li v-for="g in scopeData.games" :key="g">{ {g}}</li> </ul> </template> </Category> <Category> <template slot-scope="scopeData"> <!-- 生成的是h4标题 --> <h4 v-for="g in scopeData.games" :key="g">{ {g}}</h4> </template> </Category> 子组件中: <template> <div> <slot :games="games"></slot> </div> </template> <script> export default { name:'Category', props:['title'], //数据在子组件自身 data() { return { games:['红色警戒','穿越火线','劲舞团','超级玛丽'] } }, } </script>
-
-
子传父
逻辑:
父组件向子组件传递一个函数,子组件接收该函数,然后使用该函数,将需要的传递给父亲的值作为函数的参数传递进去。在子组件中调用了父组件的函数,并把参数传递进入了该函数中,所以父组件就得到了子组件传递进来的参数。
数组的reduce函数
reduce函数用于数组的统计,比如统计数组中男性的数量,统计价格大于100的商品
// 第二种方式: /* reduce((pre,current)=>{},0) 第一个参数是一个函数,第二个是从几开始,相当于计数器 pre:之前的值(索引),current当前的值,就是遍历的数组的每一个值 pre第一次是0,第二次是前一次该函数执行的返回值,最后一个返回值就作为reduce函数的返回值。 */ // const x = this.todos.reduce((pre, current) => { // console.log("=", pre, current); // return pre + (current.done ? 1 : 0); //条件统计 // }, 0); // console.log(x); // 精简版 return this.todos.reduce((pre, current) => pre + (current.done ? 1 : 0), 0);
总结TodoList案例
-
组件化编码流程:
(1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
1).一个组件在用:放在组件自身即可。
2). 一些组件在用:放在他们共同的父组件上(状态提升)。
(3).实现交互:从绑定事件开始。
-
props适用于:
(1).父组件 ==> 子组件 通信
(2).子组件 ==> 父组件 通信(要求父先给子一个函数)
-
使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
-
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
浏览器本地存储(webStorage)
localStorage和sessionStorage
sessionStorage和localStorage一样,不同的是session表示会话,浏览器关闭里面的数据就会消失,local则不会消失,除非手动清除。
1.存储内容一般大小支持5MB左右,不同浏览器可能不同
2.浏览器端通过Window.sessionStorage和Window.localStorage属性实现本地存储机制
3.相关的API
xxxxStorage.setItem('key','value');该方法接受一个键值对作为参数,会吧键值对添加到存储中,如果键名存在,则更新其对应的值
注意,键值对必须是字符串形式,如果传入的不是字符串,那么会自动转换为字符串
如果要讲一个对象类型作为值传入到localStorage中,需要使用JSON.stringify(p)讲该对象转换成字符串再进行转换
xxxStorage.getItem('person')该方法接收一个键名作为参数,返回键名对应的值
xxxStorage.removeItem('key')该方法接收一个键名作为参数,并将该键名从存储中删除
xxxStorage.clear()该方法会清空所有的数据
备注:
1.SessionStorage 存储的内容会随着浏览器窗口的关闭而消失
2.LocalStorage存储的内容需要手动清空才会消失(调用了clearAPI或者清空浏览器缓存)
3.xxxStorage.getItem(xxx)如果xxx对应的value获取不到,则返回null
4.JSON.parse()结果依然是null
组件的自定义事件
自定义组件的绑定
自定义事件可以实现子传父
给组件绑定事件
语法:
<Student v-on:hunnkeji="getStudentName" /> 给组件Student绑定名字是hunnkeji的自定义事件,当事件触发的时候执行getStudentName函数 v-on:事件名='函数' 给Student组件的实例对象vc绑定一个组件事件
$emit()
$emit()函数实现自定义事件的触发。
语法:
$emit(事件名,params1,params2)
事件名:需要触发的事件的名字
params:传递给事件触发时执行的函数的参数,可以是多个
进入被绑定的组件中,使用$emit()方法实现自定义事件的触发
methods: { //按钮绑定了该函数,函数内部通过$emit方法触发了hunnkeji事件 sendStudentName() { // $emit方法用于触发Student组件实例身上的hunankeji事件,this.name是要传递的数据 this.$emit("hunnkeji", this.name,1,3,44,55); // 改方法可以用来触发事件 }, },
接收事件触发时传递给函数的参数
该函数是事件触发时需要执行的函数,使用...params接收多个参数,第一个参数由name接收,后面的所有参数都会保存在params数组中 getStudentName(name, ...params) { console.log("app收到了数据学生名", name, params); },
给组件绑定事件的第二种方式
<Student ref="student" />
使用ref,ref标注的组件得到的是组件的VueComponent
因为绑定事件实际上是绑定到了组件的VueComponent上了,所有通过ref可以得到组件的VueComponent就能对vc进行操作
mounted() { setTimeout(() => { // 这一行表示:给组件绑定自定义事件,事件名为hunnkeji,事件触发的时候调用getStudentName方法 // this.$refs.student.$on("hunnkeji", this.getStudentName); // 自定义事件触发一次之后就不再触发 this.$refs.student.$once("hunnkeji", this.getStudentName); }, 3000);
操作组件的vc,this.$refs.xxx获取到组件的vc,$on实现组件事件的绑定
$on("事件名", 事件触发时别调用的函数);
这种方式更加的灵活,可以动态的实现给组件绑定事件
自定义组件的解绑
使用$off实现自定义组件的解绑
//这是一个点击回调函数 unbind() { // 解绑事件,只适合一个事件 // this.$off("hunnkeji"); // 解绑多个事件 // this.$off(["hunnkeji", "demo"]); //解绑所有的自定义事件 this.$off(); },
当我们销毁vm的时候,他的子组件以及所有的自定义事件都会被销毁,除了原生的js事件以外。如果vm被销毁了所有的子组件以及子组件的自定义事件都会被销毁。
自定义事件总结:
1.是一种组件之间的通信方式,适用于:子组件==>父组件
2.使用场景:A是父组件,B是子组件,B想给A传递数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)
3.绑定自定义事件:
1.第一种方式,在父组件中:
<Demo @hunankeji="test"/>
或者<Demo v-on:hunankeji="test">
2.第二种方式:
在父组件中 <Demo ref = "demo"> ... mounted(){ this.$refs.xxx.$on("hunankeji",this.test) }3.如果想让自定义事件只触发一次,可以使用
once
修饰符或者$once
方法<Demo v-on:hunankeji.once="test"> 。。。。 <Demo ref = 'test'> mounted(){ this.$refs.test.$once("hunankeji",this.test) }
全局事件总线(GlobalEventBus)
$on ,$off ,$emit 都在Vue的原型对象上,我们知道vc的原型的原型=Vue的原型
-
一种组件间通信的方式,适用于任意组件间通信。
-
安装全局事件总线:,在main.js中安装
new Vue({ ...... beforeCreate() { Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm }, ...... })
-
使用事件总线:
-
接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){ demo(data){......} } ...... mounted() { this.$bus.$on('xxxx',this.demo) //或者直接写成箭头回调 }
-
提供数据:
this.$bus.$emit('xxxx',数据)
-
-
最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
this.$bus.$off('xxx')
思路:
全局事件总线实现思路:
定义一个所有的组件和Vue实例都能访问的组件或者vm,作为一个中间人。
当A想给B传递数据的时候,通过这个中间人就能实现数据的传递。
B组件首先在这个中间人身上绑定一个自定义事件,当A需要给B传递数据的时候触发B绑定在中间人上的自定义事件,并传递数 据。因为这个事件的回调是在B身上的,所以B能接收到A传递过来的数据。
消息订阅与发布(pubsub)
消息订阅与发布是一种脱离Vue的组件间通信的方式,原理类似于全局事件总线。
实现步骤:
第一步;引入消息订阅发布的依赖文件。
import pubsub from "pubsub-js";
第二步:在需要接收数据的组件中进行消息订阅,在发送消息的组件中实现消息的发送。
//消息的发布 methods: { sendStudentName() { //publish('消息名',发送的内容) pubsub.publish("hello", this.name); }, }, // 消息订阅 methods: {//消息的回调,回调有两个参数,一个是消息名,一个是真实的消息 messageSubscribe(msgName, data) { console.log("有人发布了hello消息,hello消息回调执行了", msgName, data); }, }, mounted() { // this.pubid = pubsub.subscribe("hello", (msg, data) => { // console.log(this); // console.log("有人发布了hello消息,hello消息的回调执行了", msg, data); // }); this.pubid = pubsub.subscribe("hello", this.messageSubscribe); //将回调函数交给Vue管理,这里的this就是VueComponent },第三步:消息的取消订阅,当销毁之前就需要实现消息的取消订阅
beforeDestroy() { pubsub.unsubscribe(this.pubid); // 取消订阅 },
-
一种组件间通信的方式,适用于任意组件间通信。
-
使用步骤:
-
安装pubsub:
npm i pubsub-js
-
引入:
import pubsub from 'pubsub-js'
-
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
methods(){ demo(data){......} } ...... mounted() { this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息 }
-
提供数据:
pubsub.publish('xxx',数据)
-
最好在beforeDestroy钩子中,用
PubSub.unsubscribe(pid)
去取消订阅。
-
案例:
消息发送的组件
<button @click="sendSchoolName"> 通过消息订阅与发送发送学校名给Student组件 </button> methods: { sendSchoolName() { //在学校组件中发送消息: pubsub.publish("消息名", 发送的数据); pubsub.publish("hello", this.name); }, },
消息订阅的组件
mounted() { //接收数据,相当于实时监听者hello消息,只要收到hello消息,回调函数就会执行。 pubsub.subscribe("hello", (msg, data) => { console.log( "我是Student组件,有人发送了hello消息,消息名是:", msg, "消息内容是:", data ); }); },
nextTick
-
语法:
this.$nextTick(回调函数)
-
作用:在下一次 DOM 更新结束后执行其指定的回调。在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
-
什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
$nextTick()所指定的回调会在DOM节点更新之后再执行。
this.$nextTick(function(){ 这里的会在DOM节点更新之后再执行 })
Vue封装的过度与动画
vue的动画
-
作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。
-
图示:
-
写法:
-
准备好样式:
-
元素进入的样式:
-
v-enter:进入的起点
-
v-enter-active:进入过程中
-
v-enter-to:进入的终点
-
-
元素离开的样式:
-
v-leave:离开的起点
-
v-leave-active:离开过程中
-
v-leave-to:离开的终点
-
-
-
使用
<transition>
包裹要过度的元素,并配置name属性:name属性的作用是当有多个元素都要发送动画的时候,指定的动画效果给指定的元素绑定,而这个绑定的规则就是name
如果transition标签有了name属性,那么该标签所对应的动画类名也有要求,:name属性值-enter-active
appear是transition标签的属性,作用是让页面一展示就有动画效果,属性值是一个布尔值,true表示一开始就有动画。
<transition name ='enter' :appear="true">
<transition name ='enter' appear>
// 用transition标签将需要发生动画的标签包裹起来,name是指定的元素的标识 <transition name="hello"> <h1 v-show="isShow">你好啊!</h1> </transition> //准备好动画的样式 <style scoped> // 类名必须按照Vue所规定的来定义,这样就会自动给指定的标签加上动画 // 默认是v-enter-active .hello-enter-active{ animation:xx 1s ; } .hello-leave-active{ animation:xx 1s reserve; } @keyframes xx{ form{ transform:tanslateX(-100px); } to{ transform:tanslateX(0px) } } </style>
-
备注:若有多个元素需要过度,则需要使用:
<transition-group>
,且每个元素都要指定key
值。
-
vue的过渡
Vue在解析动画的时候会自动加上v-enter,v-enter-to两个样式,一个是进入的起点,一个是进入的终点。
<style scoped> h1{ // transition:0.5s linear; } //过渡效果也可以写在这里 .v-leave-active,v-enter{ transition:0.5s linear; } //进入的起点 .v-enter{ transform:translateX(-100%) } // 进入的终点 .v-enter-to{ transform:translateX(0) } //离开的起点 .v-leave{ transform:translateX(0) } // 离开的终点 .v-leave-to{ transform:translateX(-100%) } </style>
transition-group
可以控制多个元素动画
而且子元素必须要有key值
<transition-group name="hello"> <h1 key="1" v-show="isShow"></h1> <h1 key="2" v-show="!isShow"></h1> </transition-group>
集成第三方动画库
-
下载Animation.css
npm install animate.css --save
-
引入
import "animate.css"
-
name属性是固定的
<transition name="animate_animated animate_bounce"> </transition>
-
在transition中写入新的配合项
<transition name="animate_animated animate_bounce" enter-active-class="进入的动画名" leav-active-class="离开的动画"> </transition>
Vue发送Ajax
第一种方式发送Ajax:
原生new XMLHttpRequest()发送Ajax请求
第二种方式:
使用jQuery发送Ajax,淘汰,因为JQuery是对原生的DOM进行操作,但是现在我们是减少对原生的DOM进行操作
第三种方式:
axios方式,是一个ajax 。Axios是一个基于promise 的 HTTP 库,可以用在浏览器和 node.js中
第四种方式:
fetch():是一个函数,是window中内置的函数,也是promise风格,是xhr平级的,IE不支持
代理服务器
什么是代理服务器?
我们在发送ajax请求的时需要解决跨域的问题,
cors方式解决(后端解决)
jsonp解决(get请求,前后端一起搞)
代理服务器
代理服务器是解决跨域问题的一种方式。
怎么理解代理服务器?
原来我们浏览器是直接和服务器进行交互,接收服务器的数据。当我们使用ajax的时候就存在了跨域问题。跨域问题是浏览器的一种保护机制。同源策略就是浏览器的一个安全限制,它阻止了不同【域】之间进行的数据交互。
同源:协议,地址,端口都必须相同。
这个时候我们使用一个服务器,该服务器和我们的页面满足同源策略。让这个服务器去和我们真正需要的服务器进行交互,获取数据。然后我们再从这个服务器中获取数据。
下载axios
npm i axios
引入axios
在app.vue下使用
<script> import axios from 'axios', getmethods(){ axios.get("http://localhost:8080/demo/cars").then( (response) => { console.log("请求成功了", response.data); }, (error) => { console.log("请求失败了", error.message); } ); } </script>
配置代理服务器
nginx开启
vue官网 配置参考 | Vue CLI
第一步:在vue.config.js中开启代理服务器
// 开启代理服务器 devServer: { proxy: 'http://localhost:5000' 这里的地址是指代理服务器发送请求的地址//指向真实服务器,这种方式不能配置多个动态代理 }
第二步:将链接地址修改为代理服务器的端口
向代理服务器发送请求 axios.get("http://localhost:8080/students").then()
注意事项:
代理服务器什么时候不向真实服务器请求转发?
当代理服务器自己有该数据的时候就不会再向服务器发送数据。什么是代理服务器自己的数据呢?public文件就是代理服务器的根路径,只要是public中存在的就是代理服务器的数据。
配置代理服务器2
第一种动态代理的方式缺陷:
不能灵活控制该请求是否走代理
只能配置一个动态代理,不能同时配置多个代理
// 第二种配置方式 devServer: { proxy: { '/hunankeji': { // /api :请求前缀,如果存在该请求前缀就走代理,没有就不走代理 target: 'http://localhost:5000', //请求转发给谁 pathRewrite: { '^/hunankeji': '' },//将连接的/hunankeji变成空串,因为有请求前缀也发给了真实服务器路径就不对了 ws: true, //websocket changeOrigin: true //用于控制请求头中的host值。改变来源,true就是撒谎,把来源变成和目标服务器一样 //服务器会询问该请求是来自哪里,true表示撒谎,和目标服务器同源,false表示实话实话 } } }
前端
methods: { getStudents() { console.log("test"); axios.get("http://localhost:8080/hunankeji/students").then( (response) => { console.log("请求成功了", response.data); }, (error) => { console.log("请求失败了", error.message); } ); }, getCar() { axios.get("http://localhost:8080/demo/cars").then( (response) => { console.log("请求成功了", response.data); }, (error) => { console.log("请求失败了", error.message); } ); }, },
特点:
可以配置多个代理 ,且可以灵活控制请求是否走代理。加前缀就是走代理,不加就是不走代理
缺点:配置略微繁琐,请求资源时必须加前缀。
vue-resource
vue-resource是vue的一个插件,和axios用法一样,同样是实现ajax发送请求。
Vue引入第三方css
-
第一种
-
在src在assets下创建css文件夹,将资源放到css文件夹下
-
引入
<script> import "./assets/css/bootstrap.css"> // 这种方式引入会严重检查,只要有用到的没有找到就报错 </script>
-
-
第二种
-
在public中创建一个css文件夹,将css文件放到该文件夹下
-
引入,在index.html下引入
<link rel="stylesheet" href="<%= BASE_URL%>css/bootstrap.cs">
-
Vuex
什么是vuex?
概念:专门在Vue中实现集中式状态 (数据)管理的Vue插件,对vue应用中多个组件的共享状态进行集中式管理(读/写),也是一种组件之间的通信,并且适应任意间组件通信。
什么时候使用Vuex?
1.多个组件依赖于同一状态
2.来自不同组件的行为需要变更同一状态。
全局事件总线共享数据
Vuex实现多数据共享
Vuex案例分析
Actions:行为
Mutations:突变
State:状态
个人简单理解vuex
vuex是实现组件间通信的一个插件。里面存在多个部分一起工作,比如action,mutations等。
我们要使用vuex首先要引入vuex插件,
在vuex中存在一个重要的东西(store)翻译为仓库,存储。
这个store的作用是用来管理vuex中的所有的vuex中的组件,比如state,action等等
所以要使用vuex首先要有store,我们在src下创建store文件夹,在里面创建一个index.js文件,作为vuex。
在这个文件中首先要new一个store作为管理者,然后在创建各种不同的组成部分。我们最终操作的是store,通过store再操作其他的组成部分。
// 创建并暴露store export default new Vuex.Store({ actions: actions, mutations: mutations, state, getters })需要注意的是要使用vuex首先要import vuex ,然后Vue.use(Vuex)再vue中使用vuex,之后才能再main.js中导入store并使用vuex。但是因为vue脚手架对import解析规则,import store语句永远会比Vue.use(Vuex)先一步执行,但是使用store的前提是Vue使用了Vuex,还没使用就导入store就会出错。所以Vue.use(vuex)语句我们会放到store中的index.js中。
我们将需要实现共享的数据放到vuex的state中,state就是个数据存储站,需要共享的数据存储在这里。
之后我们在需要使用的组件中使用dispatch函数和Actions进行连接,Action用于处理逻辑和发送Ajax请求,在Action中使用commit和mutation进行连接,Mutation是真正实现功能的部分。在这里我们对数据进行真正需要的操作。操作之后vuex会自动是使用mutate和state进行连接,实现数据的修改。之后state会重新渲染组件。
Vuex的安装使用
第一步:
npm i vuex@3
第二步:
Vue.use(Vuex)
使用组件第三步:store,store负责管理action,mutation,state.使用组件之后就可以在vue配置项中传入store
第四步:
vc==>store
注意:vue3在2月份成为了默认版本,vue3只能用vuex4,vue2只能用vuex3,直接使用命令npm i vuex会默认安装vuex4版本。
npm i vue@3
import语法
脚手架解析import的时候,import引入外部的文件的时候,不管import语句写在什么位置,只要使用了import语句就会自动的将import语句移动到最前面执行。
import语句会将被引入的文件执行完毕之后再执行本文件中的其他内容。文件在执行的时候不管import在什么位置,都会按照书写的顺序将所有的import全部书写到文件的最上方。
console.log('test') import va from './va' //这里就会先执行va文件中的内容,然后再输出test
搭建环境
1.创建文件:src/store/index.js
//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //应用Vuex插件 Vue.use(Vuex) //准备actions对象——响应组件中用户的动作 const actions = {} //准备mutations对象——修改state中的数据 const mutations = {} //准备state对象——保存具体的数据 const state = {} //创建并暴露store export default new Vuex.Store({ actions, mutations, state })
2.在main.js
中创建vm时传入store
配置项
//引入store import store from './store' ...... //创建vm new Vue({ el:'#app', render: h => h(App), store })
基本使用
1.初始化数据、配置actions
、配置mutations
,操作文件store.js
//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //引用Vuex Vue.use(Vuex) const actions = { //响应组件中加的动作 jia(context,value){ // console.log('actions中的jia被调用了',miniStore,value) context.commit('JIA',value) }, } const mutations = { //执行加 JIA(state,value){ // console.log('mutations中的JIA被调用了',state,value) state.sum += value } } //初始化数据 const state = { sum:0 } //创建并暴露store export default new Vuex.Store({ actions, mutations, state, })
-
组件中读取vuex中的数据:
$store.state.sum
-
组件中修改vuex中的数据:
$store.dispatch('action中的方法名',数据)
或$store.commit('mutations中的方法名',数据)
备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写
dispatch
,直接编写commit
getters配置项
getters{}配置项的作用?
用于对state中的数据进行加工,有点像计算属性。
-
概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。
-
在
store.js
中追加getters
配置...... const getters = { bigSum(state){ return state.sum * 10 } } //创建并暴露store export default new Vuex.Store({ ...... getters })
-
组件中读取数据:
$store.getters.bigSum
ES6扩展运算符
原来三个点(...)真名叫扩展运算符,是在ES6中新增加的内容,它可以在函数调用/数组构造时,将数组表达式或者string在语法层面展开;还可以在构造字面量对象时将对象表达式按照key-value的方式展开。说白了就是把外层包裹去除,不管是大括号([])、花括号({}),统统不在话下
语法:
var a = [1,2,3,4]
console.log(...a)
四个map方法的使用
-
mapState方法:用于帮助我们映射
state
中的数据为计算属性computed: { //借助mapState生成计算属性:sum、school、subject(对象写法) ...mapState({sum:'sum',school:'school',subject:'subject'}), //借助mapState生成计算属性:sum、school、subject(数组写法) ...mapState(['sum','school','subject']), },
-
mapGetters方法:用于帮助我们映射
getters
中的数据为计算属性computed: { //借助mapGetters生成计算属性:bigSum(对象写法) ...mapGetters({bigSum:'bigSum'}), //借助mapGetters生成计算属性:bigSum(数组写法) ...mapGetters(['bigSum']) },
-
mapActions方法:用于帮助我们生成与
actions
对话的方法,即:包含$store.dispatch(xxx)
的函数methods:{ //靠mapActions生成:incrementOdd、incrementWait(对象形式) ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) //靠mapActions生成:incrementOdd、incrementWait(数组形式) ...mapActions(['jiaOdd','jiaWait']) }
-
mapMutations方法:用于帮助我们生成与
mutations
对话的方法,即:包含$store.commit(xxx)
的函数methods:{ //靠mapActions生成:increment、decrement(对象形式) ...mapMutations({increment:'JIA',decrement:'JIAN'}), //靠mapMutations生成:JIA、JIAN(对象形式) ...mapMutations(['JIA','JIAN']), }
备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。
Vuex模块化
我们使用Vuex进行数据共享的时候,当同时存在多个业务逻辑的时候。我们就会有多套数据的存在,比如商城项目中,有用户,订单,购物车等数据。这个时候如果将所有的数据都全部写在state中就会出现大量的不同用处的数据,产生混乱。
就需要将不同的业务变成不同的模块,方便管理。
语法:
const personCount={ state:{}, mutations:{}, actions:{}, getters:{} } //这个就是讲person的数据封装成了一个模块 ... // 创建并暴露store export default new Vuex.Store({ modules: { //使用modules配置项将模块包裹起来 a: countOption, c: personOption } })
如何在组件中使用模块化后的state中的数据呢?
computed:{ ...mapState(["countAbout", "countAbout"]), //只能写store中的数据 } //使用 { {countAbount.sum}} //使用countAbout中的sum
模块化简写形式
使用的模块化vuex中的数据简写方式
在之前我们使用模块化中的数据展示的时候,需要先写模块名.xxx的形式才能获取到模块中的数据。
下面的简写方式可以让我们之间在{ {xxx}}中通过数据名的方式使用数据。
前提,在每一个模块最开始的时候开启命名空间
namespaced:true。这是一个配置项,和state:{}是一样级别的。注意大小写
语法: ...mapState('store中存在的模块名',['模块中的state的属性名','模块中的state的属性名']) computed:{ //引入第一个模块 ...mapState("countAbout", ["sum", "school", "subject"]), //引入第二个模块 ...mapState("personAbout", ["personList", "sum"]), }
注意:
如果不同模块的state中都存在同名的属性,后引人的后覆盖前面的。
如果上述代码中两个模块都存在sum属性,第二个模块中的sum在使用中会覆盖前面一个sum
当存在多个模块的时候methods中使用mapMutations()生成方法就需要指定是哪一个模块中的mutations
methods: { // 借助mapMutation生成对应的方法,方法中会调用commit去联系mutation(对象写法) ...mapMutations("countAbout", { increment: "JIA", decrement: "JIAN" }), // ========================> // 借助mapActions生成对应的方法,方法会调用dispatch去联系action(对象形式) ...mapActions('countAbout',{ incremnetOdd: "jiaOdd", incrementWait: "jiaWait" }), },
Vuex案例:
第一步:
在src下新建store文件夹,在该文件夹内创建index.js作为vuex文件
import Vuex from 'vuex' import Vue from 'vue' Vue.use(Vuex) // 计算模块 const countOption = { namespaced: true, state: { sum: 0 }, actions: { // 因为这两个方法并没有逻辑运算并且不存在网络请求,仅仅是作为中转站的使用,所以我们可以直接和mutations进行通信,跳过这个 /* addition(context, value) { // console.log("hello", context, value) context.commit('ADDITION', value) //将数据和方法告诉mutations真正执行 }, subtract(context, value) { context.commit("SUBTRACT", value) }*/ oddNumberAdd(context, value) { if (context.state.sum % 2) { context.commit("ODDNUMBERADD", value) } }, addWait(contenxt, value) { setTimeout(() => { contenxt.commit('ADDWAIT', value) }, 1000) } }, mutations: { ADDITION(state, value) { // console.log("mutations", state, value) //第一个参数是中有state中的值,value是从actions中传递过来的值 state.sum += value }, SUBTRACT(state, value) { state.sum -= value }, ODDNUMBERADD(state, value) { state.sum += value }, ADDWAIT(state, value) { state.sum += value } }, getters: { bigNumber(state, value) { // console.log("tests", state, value) return state.sum * 10 } } } const personOption = { state: {}, actions: {}, mutations: {}, getters: {} } export default new Vuex.Store({ modules: { countOption } })
第二步:
在main.js中引入store/index.js,名字是index可以简写路径。将store在vue实例中使用。
import Vue from 'vue' import App from './App.vue' // 引入插件 import vueResource from 'vue-resource' // 引入store / index, 默认拿index文件 import store from './store' 《============================== // 关闭vue的生产提示 Vue.config.productionTip = false // 使用插件 Vue.use(vueResource); new Vue({ el: '#app', store,《================= render: h => h(App), beforeCreate() { Vue.prototype.$bus = this //安装全局事件总线,这里的this就是当前的Vue实例 } })
第三步:
在组件中使用vuex实现数据共享
Count:
<template> <div class="category"> <h1>数字的值是和:{ { sum }}</h1> <h1>数字扩大十倍:{ { bigNumber }}</h1> <select v-model="number"> <option>1</option> <option>2</option> <option>3</option> </select> <button @click="addition(number)">+</button> <button @click="subtract(number)">-</button> <button @click="oddNumberAdd(number)">奇数加</button> <button @click="addWait(number)">等一会再加</button> </div> </template> <script> import { mapState, mapActions, mapMutations, mapGetters } from "vuex"; export default { name: "Count", data() { return { number: 1, //用户选择的数字 }; }, computed: { // ...mapState(["countOption"]), 使用模块中的数据 ...mapState("countOption", ["sum"]), //使用模块中的数据 ...mapGetters("countOption", ["bigNumber"]), }, methods: { // ...mapActions("countOption", ["addition", "subtract"]), // 通过mapMutations生成方法(对象形式) ...mapMutations("countOption", { addition: "ADDITION", subtract: "SUBTRACT", }), ...mapActions("countOption", { oddNumberAdd: "oddNumberAdd", addWait: "addWait", }), }, mounted() { console.log(this.$store); }, }; </script> <style scoped> button { margin: 5px; } </style>
Person:
<template> <div> <h1>Coun组件的计算之和是:{ { he }}</h1> </div> </template> <script> import { mapState } from "vuex"; export default { name: "Person", computed: { ...mapState("countOption", { he: "sum" }), }, }; </script> <style> </style>
效果:
Vue中使用原生的css文件
在public文件夹下新建css文件夹,将css文件放到该文件夹下
在index.html中使用:
<link rel="stylesheet" href="<%= BASE_URL %>css/base.css" />
在组件中引入外部的css样式
在src下的assets文件夹下创建css文件夹,在该文件夹下创建css文件
<style scoped> @import "@/assets/css/common.css"; .header { width: 100%; height: 40px; background: #f8f8f8; } .nav-header { height: 40px; } </style>
第二种方式
使用@import引入样式文件,就算加scoped,其它没有引入的模块还是可以访问到你的样式,如果某个组件的类名一致,则就会被污染到。 如果不想被污染,修改引入方式
<style src="@/assets/css/common.css" scoped></style> <style scoped> </style>
路由(route)
什么是路由?
路由就是一组key-value对应关系。
多个路由,需要经过路由器的管理
路由的作用:实现单页面应用。只有一个页面。
vue-router
vue-router的理解
vue的一个插件库,专门用来实现SPA应用
对SPA应用的理解
1.单页面web应用(single page web application ,SPA)
2.整个应用只有一个完整的页面。
3.点击页面中的导航链接不会刷新页面,只会做页面的局部更新
4.数据需要通过ajax请求获取
路由的理解
1.一个路由是一组映射关系(key-value)
2.key为路径,value可能是function或者component
路由分类
后端路由
value是function,用于处理客户端提交的请求
工作过程:服务器接收一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
前端路由
value是component,用于展示页面内容
工作过程:当浏览器的路径发生变化的时候,对应的组件就会展示
实现路由
第一步:
首先要下载vue-router插件库。需要注意版本问题,vue2只能用router3,vue3只能用router4
npm i vue-router@3
第二步:
在main.js中引入路由
import Vue from 'vue' import App from './App.vue' // 引入路由插件 import VueRouter from "vue-router"; // 引入路由器 import router from './router' Vue.config.productionTip = false // 应用插件 Vue.use(VueRouter); new Vue({ render: h => h(App), router: router, // 使用路由器 beforeCreate() { Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm }, }).$mount('#app')
在src下创建router文件夹,在文件夹下创建js文件作为整个应用的路由器
// 该文件专门用于创建整个应用的路由器 import VueRouter from 'vue-router' //引入组件 import About from '../components/About' import Home from '../components/Home' //创建并暴露一个路由器 export default new VueRouter({ routes: [//routes表示所有的路由 { //一个{}表示的是一组路由规则 path: '/about', //跳转的路径 component: About //展示的组件 }, { path: '/home', component: Home } ] })
第三步:
在需要的地方使用路由。
<div class="list-group"> <!-- 原始html中我们使用a标签实现页面的跳转 --> <!-- <a class="list-group-item active" href="./about.html">About</a> --> <!-- <a class="list-group-item" href="./home.html">Home</a> --> <!-- Vue中借助router-link标签实现路由的切换 --> <router-link class="list-group-item" active-class="active" to="/about" >About</router-link> <router-link class="list-group-item" active-class="active" to="/home">Home</router-link> </div>router-link标签时vue-router库为我们提供的实现路由地址切换的标签,这个标签最终会在页面中转换成a标签。to表示的是跳转的地址。
第四步:
在需要展示组件的位置展示组件。
<div class="panel-body"> <!-- 指定组件的呈现位置 --> <router-view></router-view> </div>router-view标签告诉vue组件路由指定的组件应该放在什么位置。类似于插槽。
组件的分类
路由组件
通过路由规则呈现在页面中的组件是路由组件。
我们将路由组件放到pages包下。
一般组件
通过我们自己导入,配置,并使用的组件是一般组件。使用方式;<components />
我们将一般组件放到components包下。
不使用的路由组件实际上的被销毁了。
2.几个注意点
-
路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹。 -
通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
-
每个组件都有自己的
$route
属性,里面存储着自己的路由信息。所有的路由器有公共的$router
嵌套(多级)路由
什么是多级路由?
多级路由也叫嵌套路由,通过之前的学习我们知道了在前端路由就路径和组件之间的映射关系。
多级路由就是映射到的组件中还存在路由关系。
实现多级路由
第一步:
创建组件
第二步:
配置路由
// 该文件专门用于创建整个应用的路由器 import VueRouter from 'vue-router' //引入组件 import About from '../pages/About' import Home from '../pages/Home' import News from '../pages/News' import Message from '../pages/Message' //创建并暴露一个路由器 export default new VueRouter({ routes: [ { path: '/about', component: About }, { path: '/home', component: Home, ======》表示二级路由,在一级路由中嵌套路由 children: [ { path: 'news', component: News }, { path: 'message', component: Message } ] } ] })
第三步:
使用路由
<div> <ul class="nav nav-tabs"> <li> <router-link class="list-group-item" active-class="active" to="/home/news" >News</router-link > </li> <li> <router-link class="list-group-item" active-class="active" to="/home/message" >Message</router-link > </li> </ul> <router-view></router-view> </div>
注意:
二级路由的path属性中的路径不能写 “/”
在组件中使用路由的时候to=“/一级路由/二级路由”。要写完整路径
路由传参
jquery传参
路由在前端实现的就是根据路径展示对应的组件。
所谓的jquery传参就是问号传参
当我们展示组件的时候需要给对应的组件传递参数的时候就需要用到路由传参。
案例:
第一种方式实现
我们知道超链接的传参方式是在地址后面使用?key=value的形式传递参数,在路由中同样可以使用这种方式。router-link最终会转成a标签。在字符串中使用模板字符串(反引号``)引入变量
<!-- 跳转路由并携带query参数,to的字符串写法 --> <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{ { m.title}}</router-link>
接收参数
我们引入了路由之后,this中会多出两个属性$router 和 $route .
参数就存储在this.$route的query中。
<li>消息的编号:{ { this.$route.query.id }}</li> <li>消息的标题:{ { this.$route.query.title }}</li>
第二种方式实现-对象形式传递参数
在to属性中可以填写一个对象,对象的属性有path:'/xx/xx'表示的是路径。query:{}中就是要传递的参数
<!-- 跳转路由并携带query参数,to的对象写法 --> <router-link :to="{ path: '/home/message/detail', query: { id: m.id, title: m.title, }, }"> { { m.title }}</router-link>
接收方式和第一种一样。
//接收参数 <li>消息的编号:{ { this.$route.query.id }}</li> <li>消息的标题:{ { this.$route.query.title }}</li>
命名路由
命名路由的作用?
简化路由的跳转。我们之前使用三级路由的时候就必须带上一级和二级路由,二级路由就必须带上一级路由。如果路由层次越多路径就越长。
命名路由的作用就是简化路径。
在路由配置文件中写
使用方式:
在路由的配置中使用name属性给路由命名。
exprot default new VueRouter({ routes:[{ //路由名 name:"guanyu", path:'/about', component:About }] })
使用命名路由
使用命名路由必须to属性的值是一个对象。
:to="{}"
在这个对象中使用name属性替代原来的path。name属性的值是路由的名字
<router-link :to="{name: 'xiangqing', query: {id: m.id,title: m.title,},}" >{ { m.title }}</router-link> //之前的写法,path="路径 <router-link :to="{path: '/home/tes' , query: {id: m.id,title: m.title,},}" >{ { m.title }}</router-link>
params传参
路由中的params参数用来接收参数。所谓的params就是restful风格传参。参数直接在路径后面用斜杠分隔进行传递。
使用restful风格接收参数前提要在index的path属性中进行配置(占位)。告诉路由后面的是参数不是路径。
配置路由的时候声明接收
{ path:'/home', component:Home, children:[ { path:'news', component:News }, { component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', //使用占位符声明接收params参数 component:Detail } ] } ] }
2.传递参数
<!-- 跳转并携带params参数,to的字符串写法 --> <router-link :to="/home/message/detail/666/你好">跳转</router-link> <!-- 跳转并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', params:{ id:666, title:'你好' } }" >跳转</router-link>
a特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
接收参数
xxxxxxxxxx $route.params.id$route.params.title <li>消息的标题:{ { this.$route.params.title }}</li>
路由的props配置
当我传递的参数过多的时候,模板语法就会出现很多代码重复。
通过props可以接收到参数。在之前的组件学习中props可以接收外部传递给组件的东西。
路由中也存在props,用于接收外部传递给路由关系指定的组件的参数。
第一种方式:
props对象形式传递参数
语法:
props:{a:1,b'test'}
谁需要接收参数就在谁的路由中添加props配置项。
{ name: 'xiangqing', path: 'detail/:id/:title', component: Detail, // props:第一种写法,值为对象,该对象中的所有key-value都会以props形式传递给detail组件。 props: { a: 1, b: 'hello' } }
在路由组件中使用props进行接收
{ {a}} { {b}} //使用 props: ["a", "b"], //接收
注意:这种方式用的非常少,因为数据都是在路由中定义死的
第二种方式:
props:第二种写法,值为布尔值,如果布尔值为正,就会把该路由组件收到的所有params参数以props形式传递给Detail组件
语法:
props:true
,在需要的路由配置中设置props配置项的值为true,这样所有的以params参数都会以props的形式传递给对应的路由组件。案例:
{ name: 'xiangqing', path: 'detail/:id/:title', component: Detail, // props:第二种写法,值为布尔值,如果布尔值为正,就会把该路由组件收到的所有params参数以props形式传递给Detail组件 props: true }
接收:
直接在对应的组件中使用props:['xx','xx']进行接收
props: ["id", "title"], { {id}} //在需要用的地方使用
局限:这种方式只能接收params形式传递的数据,如果是jquery形式的参数就不能接收
第三种方式:
props:第三种写法,值是一个函数,该函数存在一个参数$route,该参数中会携带者我们需要的数据,有params中的query两种方式的。需要传递的参数通过函数的返回值传递。
语法:
props:function($route){return :{id:xx}}
通过该函数的参数$route可以将函数的返回值赋值。
案例:
{ name: 'xiangqing', path: 'detail', component: Detail, // props:第三种写法,值为函数 props($route) { return { id: $route.query.id, title: $route.query.title } } } //解构赋值的连续写法 { name: 'xiangqing', path: 'detail', component: Detail, // props:第三种写法,值为函数 props({query:{id,title}}) { return { id,title } } }
这种方式可以传递query和params两种类型的参数传递
<router-link>
的replace属性
-
作用:控制路由跳转时操作浏览器历史记录的模式
-
浏览器的历史记录有两种写入方式:分别为
push
和replace
,push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push
-
如何开启
replace
模式:<router-link replace .......>News</router-link>
浏览器的历史记录有两种模式,第一种是push模式,第二种是replace模式
push模式:
是一种栈模式,浏览器的历史记录存储在一个栈中,通过指针实现回退和前进。我们点击按钮的时候会将地址存储到栈中。点击浏览器的前进后退的时候指针跳转。实现页面的回退和前进
replace模式,
这种模式是当我们点击新的标签的时候将之前的历史覆盖,也就是没有撤退可言,始终只有一个。
如何实现replace?
通过在<router-link :replace:"true"></route-link> 中添加属性replace就会将历史模式替换成replace
编程式路由导航
可以不使用router-link,因为router-link最终都会转换成a标签
什么是编程式路由导航,通过编写代码操作浏览器历史的前进后退以及历史是否存在。
在历史记录中存在两种模式:push和replace。
push模式:
push模式的历史记录是以栈的形式存在,当我们进入一个路由组件,在栈内存中就会记录一条浏览地址历史。先进后出,后进先出。
replace模式:
replace模式的历史记录是覆盖式的,当我们触发新的一个路由组件的时候,就会将之前的地址覆盖。
push和replace函数
这两个函数是$router路由器原型上的方法。用于控制浏览器历史模式是push模式还是replace模式。
什么是浏览器历史模式?就是是否存在历史。push模式就是由历史记录,replace就是没有历史记录。
push函数和replace函数的参数是一个配置项,配置项中的内容和<router-link>标签的to属性中的对象中的内容一样,写的是路由的名字,参数的传递等
使用方式
<button @click="pushShow(m)">push查看</button> <button @click="replaceShow(m)">replace查看</button> methods: { pushShow(m) { // 这里的push和replace是$router的原型对象上的属性 this.$router.push({ name: "xiangqing", query: { id: m.id, title: m.title, }, }); }, replaceShow(m) { this.$router.replace({ name: "xiangqing", query: { id: m.id, title: m.title, }, }); }, },
back和forward函数
back和forward函数用于控制浏览器历史的前进和后退。这些方法都是$router 路由器上的
使用方式
<button @click="back">后退</button> <button @click="forward">前进</button> methods: { back() { this.$router.back(); }, forward() { this.$router.forward(); }, },
go函数
用于控制前进或者后退指定的步数,函数可以传递一个参数,正数表示前进,负数表示后退,值是前进或者后退的步数
使用方式:
<button @click="test">测试go函数</button> methods: { test() { this.$router.go(-1); //表示后退一步 }, }, this.$router.forward() //前进 this.$router.back() //后退 this.$router.go() //可前进也可后退
缓存路由组件
什么是缓存路由组件?
我们知道当我们切换路由的时候之前展示的组件是直接被销毁的,也就是说如果组件中存在输入框,切换组件之后,输入框中的内容全部清空。这是非常不友好的。
作用:
让不展示的路由组件保持挂载,不被销毁。
语法
<keep-alive> <router-view></router-view> //展示在这个位置的组件都会被缓存 </keep-alive> <keep-alive include=“组件名”> <router-view></router-view> //只有include中存在的组件才会被缓存 </keep-alive> <keep-alive :include=“["xx","xx"]”> <router-view></router-view> //只有include中存在的组件才会被缓存,展示多个组件的时候 </keep-alive>使用keep-alive标签将需要展示的标签进行包裹,这个时候只要展示在标签内的组件都会被缓存。
新的生命周期钩子
这两个新的钩子是路由组件独有的,当路由组件存在缓存的时候,组件并不会直接销毁,而是隐藏了。
新的生命周期钩子,是路由组件独有的。用于捕获路由组件的激活状态
activated
表示激活,路由组件被激活时触发。什么叫路由组件被激活?就是该路由组件展示在界面中我们能看到它就是被激活。
是生命周期钩子,就是一个在Vue生命周期特定的时候被调用的函数。
deactivated
表示失活,当失去展示该路由组件的时候叫失活。
路由守卫
什么是路由守卫?
相当于拦截器,拦截的是路由的切换。
作用:
保护路由的安全(权限)。就是做权限检查的,如果不符合我们设置的条件,就拦截这次请求,不放行。
全局路由守卫
什么是全局前置路由守卫?
第一个概念,全局:就是所有的路由组件都会被该路由守卫拦截,第二个概念,前置:在路由跳转之前进行拦截。
总的来说:所有的路由组件在被访问之前拦截。
全局前置路由组件
配置全局路由组件
前提条件:不能直接暴露,在暴露之前我们要对路由进行配置,所以不能直接使用export default new VueRouter(xx)。
meta:{}路由元信息,这个配置项用于存储我们自己定义的属性。值是一个对象,对象中存储我们自定义的变量。
我们可以在meta中定义一个变量用来做标记。如果某一个组件中存在着标记,则表示需要做权限验证,没有标记的直接放行。
全局前置路由守卫使用方式
const router = new VueRouter({...})我们要先接收到Vuerouter实例 //暴露之前实现路由守卫,beforeEach:每一个之前。就是表示全局的前置守卫 router.beforeEach((to,from,next)=>{}) to:表示要去哪里,就是触发事件之后到哪一个路由路径 from:从哪里来,就是跳转之前的路由路径 next:是一个函数,用于放行 export default router;//对VueRouter进行暴露
案例
// 该文件专门用于创建整个应用的路由器 import VueRouter from 'vue-router' //引入组件 import About from '../pages/About' import Home from '../pages/Home' import News from '../pages/News' import Message from '../pages/Message' import Detail from '../pages/Detail' //创建并暴露一个路由器 const router = new VueRouter({ routes: [ { name: 'guanyu', path: '/about', component: About, }, { name: 'zhuye', path: '/home', component: Home, children: [ { name: 'xinwen', path: 'news', component: News, meta: { isAuth: true } }, { name: 'xiaoxi', path: 'message', component: Message, meta: { isAuth: true }, children: [ { name: 'xiangqing', path: 'detail', component: Detail, // props:第一种写法,值为对象,该对象中的所有key-value都会以props形式传递给detail组件。 // props: { a: 1, b: 'hello' } // props:第二种写法,值为布尔值,如果布尔值为正,就会把该路由组件收到的所有params参数以props形式传递给Detail组件 // props: true // props:第三种写法,值为函数 // props($route) { // return { id: $route.query.id, title: $route.query.title } // } // 解构赋值 props({ query: { id, title } }) { return { id, title } } } ] } ] } ] }) // 全局前置路由守卫,只要路由发生切换,在切换之前该函数被调用,初始化被调用 router.beforeEach((to, from, next) => { // 回调里面有三个参数,to:去哪,点击之后的路径是哪里,from:来自哪,相当于从哪个路径来。next()放行 console.log('xxx',to, from) if (to.meta.isAuth) { // 判断路由路径是不是需要限制的路由 if (localStorage.getItem('school') === 'hunankeji') { next() } else { alert("学校名不对,无权限!!!") } } else { next() } }) export default router
全局后置路由守卫
和前置路由使用方式一样,不过是放行之后使用。里面没有next
用于做一些放行之后的操作,比如展示不同的路由组件的时候,页签显示不同的标题
使用方式:
// 后置路由守卫 router.afterEach((to, from) => { console.log("后置路由守卫:", to, from) document.title = to.meta.title || '湖南科技' // 当执行放行之后会进入这里,可以对页签进行操作。 })
独享路由守卫
某一个路由单独所有的守卫,独享路由守卫没有后置路由守卫。
独享路由守卫是某一个路由独享,名字是beforeEnter,没有后置路由守卫你
{ name: 'xinwen', path: 'news', component: News, meta: { isAuth: true, title: '新闻' }, //进入之前 beforeEnter: (to, from, next) => { //独享路由守卫 if (to.meta.isAuth) { if (localStorage.getItem('school') === 'hunankeji') { next() } else { alert("学校名不对,无权限!!!") } } else { next() } } },
组件内路由守卫
某一个组件所独有的路由守卫,只为某一个组件服务。
组件内守卫没有前置和后置守卫。只由进入和离开守卫。
注意:都是通过路由规则才会触发守卫,如果直接使用import引入组件,使用标签的形式调用不会触发
使用方式
export default { name: "About", mounted() { // console.log("test", this.$route); }, // 通过路由规则,进入该组件时被调用 beforeRouteEnter(to, from, next) { console.log("Enter", to, from); if (to.meta.isAuth) { if (localStorage.getItem("school") === "hunankeji") { next(); } else { alert("学校名不对,无权限!!!"); } } else { next(); } }, // 通过路由规则,离开组件时被调用 beforeRouteLeave(to, from, next) { console.log("About--beforeRouteLeave", to, from); next(); }, };
history模式和hash模式
浏览器地址中的/#/后面的都表示hash值。hash值不会把后面的值交给服务器。浏览器默认采用hash模式。
history模式
什么是history模式?
就是不管浏览器地址后面有什么,全部交给服务器。
hash模式
默认的模式。浏览器地址中带有/#/,/#/后面的都算是hash值,hash值不会被传递给服务器进行处理。
打包命令
npm run build
打包之后的文件必须放到服务器上部署。
使用方式:
在路由器中使用mode属性设置模式
const router = new VueRouter({ // 更改工作模式 mode: 'history', //history模式 mode:'hash' //hash模式 }路由器的两种工作模式
对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
hash模式:
地址中永远带着#号,不美观 。
若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
兼容性较好。
history模式:
地址干净,美观 。
兼容性和hash模式相比略差。
应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
ElementUI
官网学习
Element UI Element - The world's most popular Vue UI framework。
在main.js中引入elementUI
全局引入
下载elementul
npm i element-ui
import ElementUI from 'element-ui'; // 引入elementUI组件库,完整引入 // 应用插件VueRouter Vue.use(ElementUI)
这种引入方式是完整引入,所有的UI库中的所有的组件都被引入到项目中,但是实际上很多是我们不需要的,会造成应用过大。
按需引入
第一步:安装babel-plugin-component
npm install babel-plugin-component -D
第二步:将 babel.config.js 修改为:
module.exports = { presets: [ '@vue/cli-plugin-babel/preset', ["@babel/preset-env", { "modules": false }] ], "plugins": [ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ] ] }
在main.js中实现按需引入
// 引入elementUI组件库,按需引入,样式会自动引入 import { Button, Row, DatePicker } from 'element-ui'; // 默认使用方式 Vue.component(Select.name, FormItem); Vue.component(Select.name, Button); //使用组件 Vue.component('hunankeji-button', Button); Vue.component('hunankeji-row', Row); Vue.component('hunankeji-date-picker', DatePicker);
会报错
如果出现not found ,直接 npm i 引入对应的包
ES6特点
ES6反引号-模板字符串
反引号是ES6的新特性,用来表示字符串。
在模板字符串中可以直接使用变量,调用函数。
语法:
const = `${变量} //如果变量没有声明就会报错 `const = `${函数}