1.Vue核心 1.1. Vue简介 1.1.1. 官网
1.1.2. 介绍与描述
动态构建用户界面的==渐进式==JavaScript框架
作者:尤雨溪
1.1.3. Vue的特点
遵循==MVVM==模式
编码简洁,体积小,运行效率高,适合移动/PC端开发
它本身只关注UI,可以引入其它第三方库 开发项目
1.1.4.与其他JS框架的关联
借鉴 Angular 的==模板==和==数据绑定==技术
借鉴 React 的==组件化==和==虚拟DOM==技术
1.1.5. Vue周边库
vue-cli:vue脚手架
vue-resource
axios
vue-router:路由
vuex:状态管理
element-ui:基于vue的UI组件库(PC端)
1.2. 初识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 > 初识vue</title > <script src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h1 > Hello!{{name}}!</h1 > </div > <script > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ name :'JOJO' } }) </script > </body > </html >
效果:
注意:
想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法
root容器里的代码被称为==Vue模板==
Vue实例与容器是==一一对应==的
真实开发中只有一个Vue实例,并且会配合着组件一起使用
{{xxx}}
中的xxx要写==js表达式==,且xxx可以自动读取到data中的所有属性
一旦data中的数据发生变化,那么模板中用到该数据的地方也会自动更新
1.3. 模板语法 <!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 > vue模板语法</title > <script src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h1 > 插值语法</h1 > <h3 > 你好,{{name}}!</h3 > <hr > <h1 > 指令语法</h1 > <a v-bind:href ="url" > 快去看新番!</a > <br > <a :href ="url" > 快去看新番!</a > </div > <script > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ name :'JOJO' , url :'https://www.bilibili.com/' } }) </script > </body > </html >
效果:
总结:
Vue模板语法包括两大类:
插值语法:
功能:用于==解析标签体内容==
写法:{{xxx}}
,xxx是js表达式,且可以直接读取到data中的所有区域
指令语法:
功能:用于==解析标签==(包括:标签属性、标签体内容、绑定事件…)
举例:<a v-bind:href="xxx">
或简写为<a :href="xxx">
,xxx同样要写js表达式,且可以直接读取到data中的所有属性
备注:Vue中有很多的指令,且形式都是v-???
,此处我们只是拿v-bind
举个例子
1.4. 数据绑定 <!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 > 数据绑定</title > <script src ="../js/vue.js" > </script > </head > <body > <div id ="root" > 单向数据绑定:<input type ="text" v-bind:value ="name" > <br > 双向数据绑定:<input type ="text" v-model:value ="name" > </div > <script > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ name :'JOJO' } }) </script > </body > </html >
总结:
Vue中有2种数据绑定的方式:
单向绑定(v-bind
):数据只能从data流向页面
双向绑定(v-model
):数据不仅能从data流向页面,还可以从页面流向data
备注:
双向绑定一般都应用在==表单类元素==上(如:<input>
、<select>
、<textarea>
等)
v-model:value
可以简写为v-model
,因为v-model
默认收集的就是value值
1.5. el与data的两种写法 <!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 > el与data的两种写法</title > <script src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h1 > Hello,{{name}}!</h1 > </div > <script > Vue .config .productionTip = false new Vue ({ el :'#root' , data ( ){ return { name :'JOJO' } } }) </script > </body > </html >
总结:
el有2种写法:
创建Vue实例对象的时候配置el属性
先创建Vue实例,随后再通过vm.$mount('#root')
指定el的值
data有2种写法:
对象式
函数式
如何选择:目前哪种写法都可以,以后学到组件时,data必须使用函数,否则会报错
由Vue管理的函数,一定不要写箭头函数,否则this就不再是Vue实例了
1.6. MVVM模型
MVVM模型:
M:模型(Model),data中的数据
V:视图(View),模板代码
VM:视图模型(ViewModel),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 > mvvm</title > <script src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h2 > 名称:{{name}}</h2 > <h2 > 战队:{{rank}}</h2 > <h2 > 测试:{{$options}}</h2 > </div > <script > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ name :'uzi' , rank :'RNG' } }) </script > </body > </html >
效果 :
总结:
data中所有的属性,最后都出现在了vm身上
vm身上所有的属性 及 Vue原型身上所有的属性,在Vue模板中都可以直接使用
1.7. Vue中的数据代理
总结:
Vue中的数据代理==通过vm对象==来代理data对象中属性的操作(读/写)
Vue中数据代理的好处:更加方便的操作data中的数据
基本原理:
通过object.defineProperty()
把data对象中所有属性添加到vm上。
为每一个添加到vm上的属性,都指定一个getter/setter。
在getter/setter内部去操作(读/写)data中对应的属性。
1.8. 事件处理 1.8.1. 事件的基本用法 <!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 > 事件的基本用法</title > <script src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h2 > hello,{{name}}</h2 > <button v-on:click ="showInfo1" > 点我提示信息1</button > <button @click ="showInfo2($event,66)" > 点我提示信息2</button > </div > <script > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ name :'JOJO' }, methods :{ showInfo1 (event ){ console .log (event) }, showInfo2 (evnet,num ){ console .log (event,num) } } }) </script > </body > </html >
效果:
总结:
使用v-on:xxx
或@xxx
绑定事件,其中xxx是事件名
事件的回调需要配置在methods对象中,最终会在vm上
methods中配置的函数,==不要用箭头函数!==否则this就不是vm了
methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象
@click="demo
和@click="demo($event)"
效果一致,但后者可以传参
1.8.2. 事件修饰符 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > 事件修饰符</title > <script type ="text/javascript" src ="../js/vue.js" > </script > <style > *{ margin-top : 20px ; } .demo1 { height : 50px ; background-color : skyblue; } .box1 { padding : 5px ; background-color : skyblue; } .box2 { padding : 5px ; background-color : orange; } .list { width : 200px ; height : 200px ; background-color : peru; overflow : auto; } li { height : 100px ; } </style > </head > <body > <div id ="root" > <h2 > 欢迎来到{{name}}学习</h2 > <a href ="http://www.atguigu.com" @click.prevent ="showInfo" > 点我提示信息</a > <div class ="demo1" @click ="showInfo" > <button @click.stop ="showInfo" > 点我提示信息</button > </div > <button @click.once ="showInfo" > 点我提示信息</button > <div class ="box1" @click.capture ="showMsg(1)" > div1 <div class ="box2" @click ="showMsg(2)" > div2 </div > </div > <div class ="demo1" @click.self ="showInfo" > <button @click ="showInfo" > 点我提示信息</button > </div > <ul @wheel.passive ="demo" class ="list" > <li > 1</li > <li > 2</li > <li > 3</li > <li > 4</li > </ul > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ name :'尚硅谷' }, methods :{ showInfo (e ){ alert ('同学你好!' ) }, showMsg (msg ){ console .log (msg) }, demo ( ){ for (let i = 0 ; i < 100000 ; i++) { console .log ('#' ) } console .log ('累坏了' ) } } }) </script > </html >
效果:
总结:
Vue中的事件修饰符:
prevent:阻止默认事件(常用)
stop:阻止事件冒泡(常用)
once:事件只触发一次(常用)
capture:使用事件的捕获模式
self:只有event.target
是当前操作的元素时才触发事件
passive:事件的默认行为立即执行,无需等待事件回调执行完毕
修饰符可以连续写,比如可以这么用:@click.prevent.stop="showInfo"
1.8.3. 键盘事件 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > 键盘事件</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h2 > 欢迎来到{{name}}学习</h2 > <input type ="text" placeholder ="按下回车提示输入" @keydown.enter ="showInfo" > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ name :'尚硅谷' }, methods : { showInfo (e ){ console .log (e.target .value ) } }, }) </script > </html >
效果:
总结:
键盘上的每个按键都有自己的名称和编码,例如:Enter(13)。而Vue还对一些常用按键起了别名方便使用
Vue中常用的按键别名:
回车:enter
删除:delete (捕获“删除”和“退格”键)
退出:esc
空格:space
换行:tab (特殊,必须配合keydown去使用)
上:up
下:down
左:left
右:right
注意:
系统修饰键(用法特殊):ctrl、alt、shift、meta
配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
配合keydown使用:正常触发事件
可以使用keyCode去指定具体的按键,比如:@keydown.13="showInfo"
,但==不推荐这样使用==
Vue.config.keyCodes.自定义键名 = 键码
,可以自定义按键别名
1.9. 计算属性 <!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 > 计算属性</title > <script src ="../js/vue.js" > </script > </head > <body > <div id ="root" > 姓:<input type ="text" v-model ="firstName" > <br > <br > 名:<input type ="text" v-model ="lastName" > <br > <br > 姓名:<span > {{fullName}}</span > </div > <script > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ firstName :'张' , lastName :'三' }, computed :{ fullName :{ get ( ){ return this .firstName + '-' + this .lastName }, set (value ){ const arr = value.split ('-' ) this .firstName = arr[0 ] this .lastName = arr[1 ] } } } }) </script > </body > </html >
**效果: **
总结:
计算属性:
定义:要用的属性不存在,需要通过已有属性计算得来。
原理:底层借助了Objcet.defineproperty()
方法提供的getter和setter。
get函数什么时候执行?
初次读取时会执行一次
当依赖的数据发生改变时会被再次调用
==优势==:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
备注:
计算属性最终会出现在vm上,直接读取使用即可
如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变
如果计算属性确定不考虑修改,可以使用计算属性的简写形式
new Vue({ el:'#root', data:{ firstName:'张', lastName:'三' }, computed:{ fullName(){ return this.firstName + '-' + this.lastName } } })
1.10. 监视属性 1.10.1. 监视属性基本用法 <!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 > 监视属性</title > <script src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h2 > 今天天气好{{info}}!</h2 > <button @click ="changeWeather" > 点击切换天气</button > </div > <script > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ isHot :true , }, computed :{ info ( ){ return this .isHot ? '炎热' : '凉爽' } }, methods :{ changeWeather ( ){ this .isHot = !this .isHot } }, watch :{ isHot :{ immediate :true , handler (newValue,oldValue ){ console .log ('isHot被修改了' ,newValue,oldValue) } } } }) </script > </body > </html >
**效果: **
总结:
监视属性watch:
当被监视的属性变化时,回调函数自动调用,进行相关操作
监视的属性必须存在,才能进行监视
监视有两种写法:
创建Vue时传入watch配置
通过vm.$watch
监视
vm.$watch('isHot',{ immediate:true, handler(newValue,oldValue){ console.log('isHot被修改了',newValue,oldValue) } })
1.10.2. 深度监视 <!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 > 深度监视</title > <script src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h3 > a的值是:{{numbers.a}}</h3 > <button @click ="numbers.a++" > 点我让a+1</button > <h3 > b的值是:{{numbers.b}}</h3 > <button @click ="numbers.b++" > 点我让b+1</button > </div > <script > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ isHot :true , numbers :{ a :1 , b :1 , } }, watch :{ numbers :{ deep :true , handler ( ){ console .log ('numbers改变了' ) } } } }) </script > </body > </html >
效果:
总结:
深度监视:
Vue中的watch默认不监测对象内部值的改变(一层)
在watch中配置deep:true
可以监测对象内部值的改变(多层)
备注:
Vue自身可以监测对象内部值的改变,但Vue提供的watch==默认不可以==
使用watch时根据监视数据的具体结构,决定是否采用深度监视
1.10.3. 监视属性简写 如果监视属性除了handler没有其他配置项的话,可以进行简写。
<script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ isHot :true , }, computed :{ info ( ){ return this .isHot ? '炎热' : '凉爽' } }, methods : { changeWeather ( ){ this .isHot = !this .isHot } }, watch :{ isHot :{ handler (newValue,oldValue ){ console .log ('isHot被修改了' ,newValue,oldValue) } }, isHot (newValue,oldValue ){ console .log ('isHot被修改了' ,newValue,oldValue,this ) } } }) vm.$watch('isHot' ,{ handler (newValue,oldValue ){ console .log ('isHot被修改了' ,newValue,oldValue) } }) vm.$watch('isHot' ,function (newValue,oldValue ){ console .log ('isHot被修改了' ,newValue,oldValue,this ) }) </script >
1.10.4. 监听属性 VS 计算属性
使用计算属性:
new Vue({ el:'#root', data:{ firstName:'张', lastName:'三' }, computed:{ fullName(){ return this.firstName + '-' + this.lastName } } })
使用监听属性:
new Vue({ el:'#root', data:{ firstName:'张', lastName:'三', fullName:'张-三' }, watch:{ firstName(val){ setTimeout(()=>{ this.fullName = val + '-' + this.lastName },1000); }, lastName(val){ this.fullName = this.firstName + '-' + val } } }) 123456789101112131415161718
总结:
computed和watch之间的区别:
computed能完成的功能,watch都可以完成
watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
两个重要的小原则:
所有被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象
所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成==箭头函数==,这样this的指向才是vm 或 组件实例对象。
1.11. 绑定样式 <style > .basic { width : 400px ; height : 100px ; border : 1px solid black; } .happy { border : 4px solid red;; background-color : rgba (255 , 255 , 0 , 0.644 ); background : linear-gradient (30deg ,yellow,pink,orange,yellow); } .sad { border : 4px dashed rgb (2 , 197 , 2 ); background-color : gray; } .normal { background-color : skyblue; } .atguigu1 { background-color : yellowgreen; } .atguigu2 { font-size : 30px ; text-shadow :2px 2px 10px red; } .atguigu3 { border-radius : 20px ; } </style > <div id ="root" > <div class ="basic" :class ="mood" @click ="changeMood" > {{name}}</div > <br /> <br /> <div class ="basic" :class ="classArr" > {{name}}</div > <br /> <br /> <div class ="basic" :class ="classObj" > {{name}}</div > <br /> <br /> <div class ="basic" :style ="styleObj" > {{name}}</div > <br /> <br /> <div class ="basic" :style ="styleArr" > {{name}}</div > </div > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ name :'尚硅谷' , mood :'normal' , classArr :['atguigu1' ,'atguigu2' ,'atguigu3' ], classObj :{ atguigu1 :false , atguigu2 :false , }, styleObj :{ fontSize : '40px' , color :'red' , }, styleObj2 :{ backgroundColor :'orange' }, styleArr :[ { fontSize : '40px' , color :'blue' , }, { backgroundColor :'gray' } ] }, methods : { changeMood ( ){ const arr = ['happy' ,'sad' ,'normal' ] const index = Math .floor (Math .random ()*3 ) this .mood = arr[index] } }, }) </script >
效果:
总结:
class样式:
写法:class="xxx"
,xxx可以是字符串、对象、数组
字符串写法适用于:类名不确定,要动态获取
对象写法适用于:要绑定多个样式,个数不确定,名字也不确定
数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用
style样式:
:style="{fontSize: xxx}"
其中xxx是动态值
:style="[a,b]"
其中a、b是样式对象
1.12. 条件渲染 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > 条件渲染</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h2 > 当前的n值是:{{n}}</h2 > <button @click ="n++" > 点我n+1</button > <h2 v-show ="true" > Hello,{{name}}!</h2 > <div v-if ="n === 1" > Angular</div > <div v-else-if ="n === 2" > React</div > <div v-else > Vue</div > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ name :'jojo' , n :0 } }) </script > </html >
效果:
总结:
v-if:
写法:
v-if="表达式"
v-else-if="表达式"
v-else
适用于:切换频率较低的场景
特点:不展示的DOM元素直接被移除
注意:v-if
可以和v-else-if
、v-else
一起使用,但要求结构不能被==打断==
v-show:
写法:v-show="表达式"
适用于:切换频率较高的场景
特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
使用v-if
的时,元素可能无法获取到,而使用v-show
一定可以获取到
1.13. 列表渲染 1.13.1. 基本列表 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > 基本列表</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h2 > 人员列表(遍历数组)</h2 > <ul > <li v-for ="(p,index) in persons" :key ="index" > {{p.name}}-{{p.age}} </li > </ul > <h2 > 汽车信息(遍历对象)</h2 > <ul > <li v-for ="(value,k) in car" :key ="k" > {{k}}-{{value}} </li > </ul > <h2 > 遍历字符串</h2 > <ul > <li v-for ="(char,index) in str" :key ="index" > {{char}}-{{index}} </li > </ul > <h2 > 遍历指定次数</h2 > <ul > <li v-for ="(number,index) in 5" :key ="index" > {{index}}-{{number}} </li > </ul > </div > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ persons :[ {id :'001' ,name :'张三' ,age :18 }, {id :'002' ,name :'李四' ,age :19 }, {id :'003' ,name :'王五' ,age :20 } ], car :{ name :'奥迪A8' , price :'70万' , color :'黑色' }, str :'hello' } }) </script > </body > </html >
效果:
总结:
v-for
指令:
用于展示列表数据
语法:<li v-for="(item, index) in xxx" :key="yyy">
,其中key可以是index,也可以是遍历对象的唯一标识
可遍历:数组、对象、字符串(用的少)、指定次数(用的少)
1.13.2. key的作用与原理 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > key的原理</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h2 > 人员列表</h2 > <button @click.once ="add" > 添加老刘</button > <ul > <li v-for ="(p,index) in persons" :key ="index" > {{p.name}} - {{p.age}} <input type ="text" > </li > </ul > </div > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ persons :[ {id :'001' ,name :'张三' ,age :18 }, {id :'002' ,name :'李四' ,age :19 }, {id :'003' ,name :'王五' ,age :20 } ] }, methods : { add ( ){ const p = {id :'004' ,name :'老刘' ,age :40 } this .persons .unshift (p) } }, }) </script > </html >
效果:
原理:
面试题 :react 、vue中的key有什么作用?(key的内部原理)
虚拟DOM中key的作用:key是虚拟DOM中==对象的标识==,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
对比规则:
旧虚拟DOM中找到了与新虚拟DOM相同的key:
若虚拟DOM中内容没变, 直接使用之前的真实DOM
若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
旧虚拟DOM中未找到与新虚拟DOM相同的key:创建新的真实DOM,随后渲染到到页面
用index作为key可能会引发的问题:
若对数据进行逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低
若结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题
开发中如何选择key?
最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值
如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表,使用index作为key是没有问题的
1.13.3. 列表过滤 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > 列表过滤</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h2 > 人员列表</h2 > <input type ="text" placeholder ="请输入名字" v-model ="keyWord" > <ul > <li v-for ="(p,index) of filPersons" :key ="index" > {{p.name}}-{{p.age}}-{{p.sex}} </li > </ul > </div > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ keyWord :'' , persons :[ {id :'001' ,name :'马冬梅' ,age :19 ,sex :'女' }, {id :'002' ,name :'周冬雨' ,age :20 ,sex :'女' }, {id :'003' ,name :'周杰伦' ,age :21 ,sex :'男' }, {id :'004' ,name :'温兆伦' ,age :22 ,sex :'男' } ] }, computed :{ filPersons ( ){ return this .persons .filter ((p )=> { return p.name .indexOf (this .keyWord ) !== -1 }) } } }) </script > </body > </html >
效果:
1.13.4. 列表排序 <body > <div id ="root" > <h2 > 人员列表</h2 > <input type ="text" placeholder ="请输入名字" v-model ="keyWord" > <button @click ="sortType = 2" > 年龄升序</button > <button @click ="sortType = 1" > 年龄降序</button > <button @click ="sortType = 0" > 原顺序</button > <ul > <li v-for ="(p,index) of filPersons" :key ="p.id" > {{p.name}}-{{p.age}}-{{p.sex}} </li > </ul > </div > <script > new Vue ({ el :'#root' , data :{ persons :[ {id :'001' ,name :'马冬梅' ,age :30 ,sex :'女' }, {id :'002' ,name :'周冬雨' ,age :45 ,sex :'女' }, {id :'003' ,name :'周杰伦' ,age :21 ,sex :'男' }, {id :'004' ,name :'温兆伦' ,age :22 ,sex :'男' } ], keyWord :'' , sortType :0 , }, 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 >
效果:
1.13.5. Vue数据监视 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > Vue数据监视</title > <style > button { margin-top : 10px ; } </style > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h1 > 学生信息</h1 > <button @click ="student.age++" > 年龄+1岁</button > <br /> <button @click ="addSex" > 添加性别属性,默认值:男</button > <br /> <button @click ="addFriend" > 在列表首位添加一个朋友</button > <br /> <button @click ="updateFirstFriendName" > 修改第一个朋友的名字为:张三</button > <br /> <button @click ="addHobby" > 添加一个爱好</button > <br /> <button @click ="updateHobby" > 修改第一个爱好为:开车</button > <br /> <button @click ="removeSmoke" > 过滤掉爱好中的抽烟</button > <br /> <h3 > 姓名:{{student.name}}</h3 > <h3 > 年龄:{{student.age}}</h3 > <h3 v-if ="student.sex" > 性别:{{student.sex}}</h3 > <h3 > 爱好:</h3 > <ul > <li v-for ="(h,index) in student.hobby" :key ="index" > {{h}} </li > </ul > <h3 > 朋友们:</h3 > <ul > <li v-for ="(f,index) in student.friends" :key ="index" > {{f.name}}--{{f.age}} </li > </ul > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ student :{ name :'tom' , age :18 , hobby :['抽烟' ,'喝酒' ,'烫头' ], friends :[ {name :'jerry' ,age :35 }, {name :'tony' ,age :36 } ] } }, methods : { addSex ( ){ this .$set(this .student ,'sex' ,'男' ) }, addFriend ( ){ this .student .friends .unshift ({name :'jack' ,age :70 }) }, updateFirstFriendName ( ){ this .student .friends [0 ].name = '张三' }, addHobby ( ){ this .student .hobby .push ('学习' ) }, updateHobby ( ){ this .student .hobby .splice (0 ,1 ,'开车' ) }, removeSmoke ( ){ this .student .hobby = this .student .hobby .filter ((h )=> { return h !== '抽烟' }) } } }) </script > </html >
效果:
总结:
Vue监视数据的原理:
vue会监视data中所有层次的数据
如何监测对象中的数据?
通过setter实现监视,且要在new Vue
时就传入要监测的数据
对象中后追加的属性,Vue默认不做响应式处理
如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value)
vm.$set(target,propertyName/index,value)
如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
调用原生对应的方法对数组进行更新
重新解析模板,进而更新页面
在Vue修改数组中的某个元素一定要用如下方法:
使用这些API:push()
、pop()
、shift()
、unshift()
、splice()
、sort()
、reverse()
Vue.set()
或 vm.$set()
特别注意 :Vue.set()
和 vm.$set()
不能给vm 或 vm的根数据对象(data等) 添加属性
1.14. 收集表单数据 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > 收集表单数据</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <form @submit.prevent ="demo" > 账号:<input type ="text" v-model.trim ="userInfo.account" > <br /> <br /> 密码:<input type ="password" v-model ="userInfo.password" > <br /> <br /> 年龄:<input type ="number" v-model.number ="userInfo.age" > <br /> <br /> 性别: 男<input type ="radio" name ="sex" v-model ="userInfo.sex" value ="male" > 女<input type ="radio" name ="sex" v-model ="userInfo.sex" value ="female" > <br /> <br /> 爱好: 学习<input type ="checkbox" v-model ="userInfo.hobby" value ="study" > 打游戏<input type ="checkbox" v-model ="userInfo.hobby" value ="game" > 吃饭<input type ="checkbox" v-model ="userInfo.hobby" value ="eat" > <br /> <br /> 所属校区: <select v-model ="userInfo.city" > <option value ="" > 请选择校区</option > <option value ="beijing" > 北京</option > <option value ="shanghai" > 上海</option > <option value ="shenzhen" > 深圳</option > <option value ="wuhan" > 武汉</option > </select > <br /> <br /> 其他信息: <textarea v-model.lazy ="userInfo.other" > </textarea > <br /> <br /> <input type ="checkbox" v-model ="userInfo.agree" > 阅读并接受<a href ="http://www.atguigu.com" > 《用户协议》</a > <button > 提交</button > </form > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ userInfo :{ account :'' , password :'' , age :0 , sex :'female' , hobby :[], city :'beijing' , other :'' , agree :'' } }, methods : { demo ( ){ console .log (JSON .stringify (this .userInfo )) } } }) </script > </html >
效果:
总结:
收集表单数据:
v-model
的三个修饰符:
lazy:失去焦点后再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤
1.15. 过滤器 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > 过滤器</title > <script type ="text/javascript" src ="../js/vue.js" > </script > <script src ="https://cdn.bootcdn.net/ajax/libs/dayjs/1.10.6/dayjs.min.js" > </script > </head > <body > <div id ="root" > <h2 > 时间</h2 > <h3 > 当前时间戳:{{time}}</h3 > <h3 > 转换后时间:{{time | timeFormater()}}</h3 > <h3 > 转换后时间:{{time | timeFormater('YYYY-MM-DD HH:mm:ss')}}</h3 > <h3 > 截取年月日:{{time | timeFormater() | mySlice}}</h3 > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false Vue .filter ('mySlice' ,function (value ){ return value.slice (0 ,11 ) }) new Vue ({ el :'#root' , data :{ time :1626750147900 , }, filters :{ timeFormater (value, str="YYYY年MM月DD日 HH:mm:ss" ){ return dayjs (value).format (str) } } }) </script > </html >
效果:
总结:
过滤器:
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:
注册过滤器:Vue.filter(name,callback)
或 new Vue{filters:{}}
使用过滤器:{{ xxx | 过滤器名}}
或 v-bind:属性 = "xxx | 过滤器名"
备注:
过滤器可以接收额外参数,多个过滤器也可以串联
并没有改变原本的数据,而是产生新的对应的数据
1.16. 内置指令 1.16.1. v-text指令 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > v-text指令</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <div > 你好,{{name}}</div > <div v-text ="name" > </div > <div v-text ="str" > </div > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ name :'JOJO' , str :'<h3>你好啊!</h3>' } }) </script > </html >
总结:
之前学过的指令:
v-bind
:单向绑定解析表达式,可简写为:
v-model
:双向数据绑定
v-for
:遍历数组 / 对象 / 字符串
v-on
:绑定事件监听,可简写为@
v-if
:条件渲染(动态控制节点是否存存在)
v-else
:条件渲染(动态控制节点是否存存在)
v-show
:条件渲染 (动态控制节点是否展示)
v-text
指令:
作用:向其所在的节点中渲染文本内容
与插值语法的区别:v-text
会替换掉节点中的内容,{{xx}}
则不会。
1.16.2. v-html指令 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > v-html指令</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <div > Hello,{{name}}</div > <div v-html ="str" > </div > <div v-html ="str2" > </div > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ name :'JOJO' , str :'<h3>你好啊!</h3>' , str2 :'<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>' , } }) </script > </html >
总结:
v-html
指令:
作用:向指定节点中渲染包含html结构的内容
与插值语法的区别:
v-html
会替换掉节点中所有的内容,{{xx}}
则不会
v-html
可以识别html结构
严重注意:v-html
有安全性问题!!!
在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击
一定要在可信的内容上使用v-html
,永远不要用在用户提交的内容上!!!
1.16.3. v-cloak指令 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > v-cloak指令</title > <style > [v-cloak] { display :none; } </style > </head > <body > <div id ="root" > <h2 v-cloak > {{name}}</h2 > </div > <script type ="text/javascript" src ="../js/vue.js" > </script > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ name :'尚硅谷' } }) </script > </html >
总结:
v-cloak
指令(没有值):
本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak
属性
使用css配合v-cloak
可以解决网速慢时页面展示出{{xxx}}
的问题
1.16.4. v-once指令 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > v-once指令</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h2 v-once > n初始化的值是:{{n}}</h2 > <h2 > n现在的值是:{{n}}</h2 > <button @click ="n++" > 点我n+1</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ n :1 } }) </script > </html >
总结:
v-once
指令:
v-once
所在节点在初次动态渲染后,就视为静态内容了
以后数据的改变不会引起v-once
所在结构的更新,可以用于优化性能
1.16.5. v-pre指令 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > v-pre指令</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h2 v-pre > Vue其实很简单</h2 > <h2 > 当前的n值是:{{n}}</h2 > <button @click ="n++" > 点我n+1</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ n :1 } }) </script > </html >
总结:
v-pre
指令:
跳过其所在节点的编译过程。
可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
1.17. 自定义指令 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > 自定义指令</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h2 > 当前的n值是:<span v-text ="n" > </span > </h2 > <h2 > 放大10倍后的n值是:<span v-big ="n" > </span > </h2 > <button @click ="n++" > 点我n+1</button > <hr /> <input type ="text" v-fbind:value ="n" > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ n :1 }, directives :{ big (element,binding ){ console .log ('big' ,this ) element.innerText = binding.value * 10 }, fbind :{ bind (element,binding ){ element.value = binding.value }, inserted (element,binding ){ element.focus () }, update (element,binding ){ element.value = binding.value } } } }) </script > </html >
总结:
自定义指令定义语法:
局部指令:
new Vue({
directives:{指令名:配置对象}
})
123
2. ```javascript new Vue({ directives:{指令名:回调函数} }) 123
全局指令:
Vue.directive(指令名,配置对象)
Vue.directive(指令名,回调函数)
例如:
Vue .directive ('fbind' ,{ bind (element,binding ){ element.value = binding.value }, inserted (element,binding ){ element.focus () }, update (element,binding ){ element.value = binding.value } }) 1234567891011121314
配置对象中常用的3个回调函数:
bind(element,binding)
:指令与元素成功绑定时调用
inserted(element,binding)
:指令所在元素被插入页面时调用
update(element,binding)
:指令所在模板结构被重新解析时调用
备注:
指令定义时不加“v-”,但使用时要加“v-”
指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名
new Vue ({ el :'#root' , data :{ n :1 }, directives :{ 'big-number' (element,binding){ element.innerText = binding.value * 10 } } })
1.18. Vue生命周期 1.18.1. 引出生命周期 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > 引出生命周期</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h2 v-if ="a" > 你好啊</h2 > <h2 :style ="{opacity}" > 欢迎学习Vue</h2 > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ a :false , opacity :1 }, mounted ( ){ console .log ('mounted' ,this ) setInterval (() => { this .opacity -= 0.01 if (this .opacity <= 0 ) this .opacity = 1 },16 ) }, }) </script > </html >
总结:
生命周期:
又名:生命周期回调函数、生命周期函数、生命周期钩子
是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数
生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
生命周期函数中的this指向是vm 或 组件实例对象
1.18.2. 分析生命周期
<!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > 分析生命周期</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h2 v-text ="n" > </h2 > <h2 > 当前的n值是:{{n}}</h2 > <button @click ="add" > 点我n+1</button > <button @click ="bye" > 点我销毁vm</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ n :1 }, methods : { add ( ){ console .log ('add' ) this .n ++ }, bye ( ){ console .log ('bye' ) this .$destroy() } }, watch :{ n ( ){ console .log ('n变了' ) } }, beforeCreate ( ) { console .log ('beforeCreate' ) }, created ( ) { console .log ('created' ) }, beforeMount ( ) { console .log ('beforeMount' ) }, mounted ( ) { console .log ('mounted' ) }, beforeUpdate ( ) { console .log ('beforeUpdate' ) }, updated ( ) { console .log ('updated' ) }, beforeDestroy ( ) { console .log ('beforeDestroy' ) }, destroyed ( ) { console .log ('destroyed' ) }, }) </script > </html >
1.18.3. 总结生命周期 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > 引出生命周期</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h2 :style ="{opacity}" > 欢迎学习Vue</h2 > <button @click ="opacity = 1" > 透明度设置为1</button > <button @click ="stop" > 点我停止变换</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ opacity :1 }, methods : { stop ( ){ this .$destroy() } }, mounted ( ){ console .log ('mounted' ,this ) this .timer = setInterval (() => { console .log ('setInterval' ) this .opacity -= 0.01 if (this .opacity <= 0 ) this .opacity = 1 },16 ) }, beforeDestroy ( ) { clearInterval (this .timer ) console .log ('vm即将驾鹤西游了' ) }, }) </script > </html >
总结:
常用的生命周期钩子:
mounted
:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等初始化操作
beforeDestroy
:清除定时器、解绑自定义事件、取消订阅消息等收尾工作
关于销毁Vue实例:
销毁后借助Vue开发者工具看不到任何信息
销毁后自定义事件会失效,但原生DOM事件依然有效
一般不会在beforeDestroy
操作数据,因为即便操作数据,也不会再触发更新流程了
2.Vue组件化编程 2.1. 模块与组件、模块化与组件化
2.1.1. 模块
理解:向外提供特定功能的 js 程序,一般就是一个 js 文件
为什么:js 文件很多很复杂
作用:复用 js,简化 js 的编写,提高 js 运行效率
2.1.2. 组件
定义:用来实现局部功能的代码和资源的集合(html/css/js/image…)
为什么:一个界面的功能很复杂
作用:复用编码,简化项目编码,提高运行效率
2.1.3. 模块化 当应用中的 js 都以模块来编写的,那这个应用就是一个模块化的应用
2.1.4. 组件化 当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用
2.2. 非单文件组件 2.2.1. 基本使用 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > 基本使用</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h1 > {{msg}}</h1 > <hr > <school > </school > <hr > <student > </student > </div > </body > <script type ="text/javascript" > Vue.config.productionTip = false //第一步:创建school组件 const school = Vue.extend({ //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。 template:` <div class ="demo" > <h2 > 学校名称: {{schoolName }} </h2 > <h2 > 学校地址: {{address }} </h2 > </div > `, data(){ return { schoolName:'尚硅谷', address:'北京昌平' } } }) //第一步:创建student组件 const student = Vue.extend({ template:` <div > <h2 > 学生姓名: {{studentName }} </h2 > <h2 > 学生年龄: {{age }} </h2 > </div > `, data(){ return { studentName:'JOJO', age:20 } } }) //创建vm new Vue({ el:'#root', data:{ msg:'你好,JOJO!' }, //第二步:注册组件(局部注册) components:{ school, student } }) </script > </html >
效果:
总结:
2.2.2. 组件注意事项 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > 组件注意事项</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <h1 > {{msg}}</h1 > <school > </school > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const school = Vue .extend ({ name :'atguigu' , template :` <div> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> ` , data ( ){ return { name :'尚硅谷' , address :'北京' } } }) new Vue ({ el :'#root' , data :{ msg :'欢迎学习Vue!' }, components :{ school } }) </script > </html >
**效果: **
总结:
关于组件名:
一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School
多个单词组成:
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
备注:
组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行
可以使用name配置项指定组件在开发者工具中呈现的名字
关于组件标签:
第一种写法:<school></school>
第二种写法:<school/>
备注:不使用脚手架时,<school/>
会导致后续组件不能渲染
一个简写方式:const school = Vue.extend(options)
可简写为:const school = options
2.2.3. 组件的嵌套 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > 组件的嵌套</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > </div > </body > <script type ="text/javascript" > Vue.config.productionTip = false //定义student组件 const student = Vue.extend({ template:` <div > <h2 > 学生名称: {{name }} </h2 > <h2 > 学生年龄: {{age }} </h2 > </div > `, data(){ return { name:'JOJO', age:20 } } }) //定义school组件 const school = Vue.extend({ template:` <div > <h2 > 学校名称: {{name }} </h2 > <h2 > 学校地址: {{address }} </h2 > <student > </student > </div > `, components:{ student }, data(){ return { name:'尚硅谷', address:'北京' } } }) //定义hello组件 const hello = Vue.extend({ template:` <h1 > {{msg }} </h1 > `, data(){ return { msg:"欢迎学习尚硅谷Vue教程!" } } }) //定义app组件 const app = Vue.extend({ template:` <div > <hello > </hello > <school > </school > </div > `, components:{ school, hello } }) //创建vm new Vue({ template:` <app > </app > `, el:'#root', components:{ app } }) </script > </html >
2.2.4. VueComponent 关于VueComponent:
school组件本质是一个名为VueComponent
的构造函数,且不是程序员定义的,是Vue.extend
生成的
我们只需要写<school/>
或<school></school>
,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)
特别注意:每次调用Vue.extend
,返回的都是一个==全新==的VueComponent!
关于this指向:
组件配置中:data
函数、methods
中的函数、watch
中的函数、computed
中的函数 它们的this均是==VueComponent实例对象==
new Vue(options)
配置中:data
函数、methods
中的函数、watch
中的函数、computed
中的函数 它们的this均是==Vue实例对象==
VueComponent
的实例对象,以后简称vc(也可称之为:组件实例对象)
Vue
的实例对象,以后简称vm
只有在本笔记中VueComponent
的实例对象才简称为vc
2.2.5. 一个重要的内置关系 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" /> <title > 一个重要的内置关系</title > <script type ="text/javascript" src ="../js/vue.js" > </script > </head > <body > <div id ="root" > <school > </school > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false Vue .prototype .x = 99 const school = Vue .extend ({ name :'school' , template :` <div> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> <button @click="showX">点我输出x</button> </div> ` , data ( ){ return { name :'尚硅谷' , address :'北京' } }, methods : { showX ( ){ console .log (this .x ) } }, }) const vm = new Vue ({ el :'#root' , data :{ msg :'你好' }, components :{school} }) </script > </html >
总结:
一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue 原型上的属性、方法
2.3. 单文件组件
School.vue:
<template> <div id='Demo'> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> <button @click="showName">点我提示学校名</button> </div> </template> <script> export default { name:'School', data() { return { name:'尚硅谷', address:'北京' } }, methods: { showName(){ alert(this.name) } }, } </script> <style> #Demo{ background: orange; } </style>
Student.vue:
<template> <div> <h2>学生姓名:{{name}}</h2> <h2>学生年龄:{{age}}</h2> </div> </template> <script> export default { name:'Student', data() { return { name:'JOJO', age:20 } }, } </script>
App.vue:
<template> <div> <School></School> <Student></Student> </div> </template> <script> import School from './School.vue' import Student from './Student.vue' export default { name:'App', components:{ School, Student } } </script>
main.js:
import App from './App.vue' new Vue ({ template :`<App></App>` , el :'#root' , components :{App } })
index.html
<!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 > 单文件组件练习</title > </head > <body > <div id ="root" > </div > <script src ="../../js/vue.js" > </script > <script src ="./main.js" > </script > </body > </html >
3.使用Vue CLI脚手架 3.1. 初始化脚手架 3.1.1. 说明
Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)
最新的版本是 4.x
文档:Vue CLI
3.1.2. 具体步骤
如果下载缓慢请配置 npm 淘宝镜像:npm config set registry http://registry.npm.taobao.org
全局安装@vue/cli:npm install -g @vue/cli
切换到你要创建项目的目录,然后使用命令创建项目:vue create xxxx
选择使用vue的版本
启动项目:npm run serve
暂停项目:Ctrl+C
Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置,请执行:
3.1.3. 分析脚手架结构 脚手架文件结构:
.文件目录 ├── node_modules ├── public │ ├── favicon.ico: 页签图标 │ └── index.html: 主页面 ├── src │ ├── assets: 存放静态资源 │ │ └── logo.png │ │── component: 存放组件 │ │ └── HelloWorld.vue │ │── App.vue: 汇总所有组件 │ └── main.js: 入口文件 ├── .gitignore: git版本管制忽略的配置 ├── babel.config.js: babel的配置文件 ├── package.json: 应用包配置文件 ├── README.md: 应用描述文件 └── package-lock.json: 包版本控制文件
src/components/School.vue
:
<template> <div id='Demo'> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> <button @click="showName">点我提示学校名</button> </div> </template> <script> export default { name:'School', data() { return { name:'尚硅谷', address:'北京' } }, methods: { showName() { alert(this.name) } }, } </script> <style> #Demo{ background: orange; } </style>
src/components/Student.vue
:
<template> <div> <h2>学生姓名:{{name}}</h2> <h2>学生年龄:{{age}}</h2> </div> </template> <script> export default { name:'Student', data() { return { name:'JOJO', age:20 } }, } </script>
src/App.vue
:
<template> <div> <School></School> <Student></Student> </div> </template> <script> import School from './components/School.vue' import Student from './components/Student.vue' export default { name:'App', components:{ School, Student } } </script>
src/main.js
:
import Vue from 'vue' import App from './App.vue' Vue .config .productionTip = false new Vue ({ el :'#app' , render : h => h (App ), })
public/index.html
:
<!DOCTYPE html > <html lang ="" > <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" > <link rel ="icon" href ="<%= BASE_URL %>favicon.ico" > <title > <%= htmlWebpackPlugin.options.title %></title > </head > <body > <div id ="app" > </div > </body > </html >
效果:
3.1.4. render函数 import Vue from 'vue' import App from './App.vue' Vue .config .productionTip = false new Vue ({ el :'#app' , render : h => h (App ), })
总结:
关于不同版本的函数:
vue.js
与 vue.runtime.xxx.js
的区别:
vue.js
是完整版的 Vue,包含:核心功能+模板解析器
vue.runtime.xxx.js
是运行版的 Vue,只包含核心功能,没有模板解析器
因为 vue.runtime.xxx.js
没有模板解析器,所以不能使用 template
配置项,需要使用 render
函数接收到的createElement
函数去指定具体内容
3.1.5. 修改默认配置
vue.config.js
是一个可选的配置文件,如果项目的(和 package.json
同级的)根目录中存在这个文件,那么它会被 @vue/cli-service
自动加载
使用 vue.config.js
可以对脚手架进行个性化定制,详见配置参考 | Vue CLI
const { defineConfig } = require ('@vue/cli-service' )module .exports = defineConfig ({ transpileDependencies : true , lintOnSave :false })
3.2. ref属性 <template> <div> <h1 ref="title">{{msg}}</h1> <School ref="sch"/> <button @click="show" ref="btn">点我输出ref</button> </div> </template> <script> import School from './components/School.vue' export default { name:'App', components: { School }, data() { return { msg:'欢迎学习Vue!' } }, methods:{ show(){ console.log(this.$refs.title) console.log(this.$refs.sch) console.log(this.$refs.btn) } } } </script>
效果:
总结:
ref
属性:
被用来给元素或子组件注册引用信息(id的替代者)
应用在html
标签上获取的是真实DOM元素,应用在组件标签上获取的是组件实例对象(vc)
使用方式:
打标识:<h1 ref="xxx"></h1>
或 <School ref="xxx"></School>
获取:this.$refs.xxx
.
3.3. props配置项
src/App.vue
:
<template>
<div>
<Student name="JOJO" sex="男酮" :age="20" />
</div>
</template>
<script>
import Student from './components/Student.vue'
export default {
name:'App',
components: { Student },
}
</script>
- `src/components/Student.vue`: - ```vue <template> <div> <h1>{{msg}}</h1> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <h2>学生年龄:{{age}}</h2> </div> </template> <script> export default { name:'Student', data() { return { msg:"我是一名来自枝江大学的男酮,嘿嘿,我的金轮~~", } }, // 简单声明接收 // props:['name','age','sex'] // 接收的同时对数据进行类型限制 /* props:{ name:String, age:Number, sex:String } */ // 接收的同时对数据进行类型限制 + 指定默认值 + 限制必要性 props:{ name:{ type:String, required:true, }, age:{ type:Number, default:99 }, sex:{ type:String, required:true } } } </script>
效果:
总结:
props
配置项:
功能:让组件接收外部传过来的数据
传递数据:<Demo name="xxx"/>
接收数据:
第一种方式(只接收):props:['name']
第二种方式(限制数据类型):props:{name:String}
第三种方式(限制类型、限制必要性、指定默认值):
props :{ name :{ type :String , required :true , default :'JOJO' } }
props是==只读的==,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据
3.4. mixin混入 局部混入:
src/mixin.js
:
export const mixin = { methods : { showName ( ) { alert (this .name ) } }, mounted ( ) { console .log ("你好呀~" ) } }
src/components/School.vue
:
<template> <div > <h2 @click ="showName" > 学校姓名:{{name}}</h2 > <h2 > 学校地址:{{address}}</h2 > </div > </template> <script > import {mixin} from '../mixin' export default { name :'School' , data ( ) { return { name :'尚硅谷' , address :'北京' } }, mixins :[mixin] } </script >
src/components/Student.vue
:
<template> <div> <h2 @click="showName">学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> </div> </template> <script> //引入混入 import {mixin} from '../mixin' export default { name:'Student', data() { return { name:'JOJO', sex:'男' } }, mixins:[mixin] } </script>
src/App.vue
:
<template> <div> <School/> <hr/> <Student/> </div> </template> <script> import Student from './components/Student.vue' import School from './components/School.vue' export default { name:'App', components: { Student,School }, } </script>
效果:
全局混入:
src/main.js
:
import Vue from 'vue' import App from './App.vue' import {mixin} from './mixin' Vue .config .productionTip = false Vue .mixin (mixin)new Vue ({ el :"#app" , render : h => h (App ) })
总结:
mixin
(混入):
功能:可以==把多个组件共用的配置提取成一个混入对象==.
使用方式:
定义混入:
const mixin = { data ( ){....}, methods :{....} .... }
使用混入:
全局混入: Vue.mixin(xxx)
局部混入: mixins: ['xxx']
备注:
组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”,在发生冲突时==以组件优先==。
var mixin = { data : function ( ) { return { message : 'hello' , foo : 'abc' } } } new Vue ({ mixins : [mixin], data () { return { message : 'goodbye' , bar : 'def' } }, created () { console .log (this .$data ) } })
同名生命周期钩子将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
var mixin = { created () { console .log ('混入对象的钩子被调用' ) } } new Vue ({ mixins : [mixin], created () { console .log ('组件钩子被调用' ) } })
3.5. plugin插件 ``src/plugin.js:
export default { install (Vue,x,y,z ){ console .log (x,y,z) Vue .filter ('mySlice' ,function (value ){ return value.slice (0 ,4 ) }) Vue .mixin ({ data ( ) { return { x :100 , y :200 } }, }) Vue .prototype .hello = ()=> {alert ('你好啊' )} } }
src/main.js
:
import Vue from 'vue' import App from './App.vue' import plugin from './plugin' Vue .config .productionTip = false Vue .use (plugin,1 ,2 ,3 )new Vue ({ el :"#app" , render : h => h (App ) })
src/components/School.vue
:
<template> <div> <h2>学校姓名:{{name | mySlice}}</h2> <h2>学校地址:{{address}}</h2> </div> </template> <script> export default { name:'School', data() { return { name:'尚硅谷atguigu', address:'北京' } } } </script>
src/components/Student.vue
:
<template> <div> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <button @click="test">点我测试hello方法</button> </div> </template> <script> export default { name:'Student', data() { return { name:'JOJO', sex:'男' } }, methods:{ test() { this.hello() } } } </script>
效果:
总结:
插件:
功能:用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据
定义插件:
plugin.install = function (Vue, options ) { Vue .filter (....) Vue .directive (....) Vue .mixin (....) Vue .prototype .$myMethod = function ( ) {...} Vue .prototype .$myProperty = xxxx }
使用插件: Vue.use(plugin)
3.6. scoped样式 src/components/School.vue
:
<template> <div class="demo"> <h2>学校姓名:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> </template> <script> export default { name:'School', data() { return { name:'尚硅谷', address:'北京' } } } </script> <style scoped> .demo{ background-color: blueviolet; } </style>
src/components/Student.vue
<template> <div class="demo"> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> </div> </template> <script> export default { name:'Student', data() { return { name:'JOJO', sex:'男' } } } </script> <style scoped> .demo{ background-color: chartreuse; } </style>
src/App.vue
:
<template> <div> <School/> <Student/> </div> </template> <script> import Student from './components/Student.vue' import School from './components/School.vue' export default { name:'App', components: { Student,School }, } </script>
效果:
总结:
scoped
样式:
作用:让样式在局部生效,防止冲突
写法:<style scoped>
scoped
样式一般不会在App.vue
中使用
3.7. Todo-List案例 src/components/MyHeader.vue
:
<template> <div class="todo-header"> <input type="text" placeholder="请输入你的任务名称,按回车键确认" @keydown.enter="add" v-model="title"/> </div> </template> <script> import {nanoid} from 'nanoid' export default { name:'MyHeader', data() { return { title:'' } }, methods:{ add(){ if(!this.title.trim()) return const todoObj = {id:nanoid(),title:this.title,done:false} this.addTodo(todoObj) this.title = '' } }, props:['addTodo'] } </script> <style scoped> .todo-header input { width: 560px; height: 28px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px; padding: 4px 7px; } .todo-header input:focus { outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); } </style>
src/components/MyItem.vue
:
<template> <li> <label> <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/> <span>{{todo.title}}</span> </label> <button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button> </li> </template> <script> export default { name:'MyItem', props:['todo','checkTodo','deleteTodo'], methods:{ handleCheck(id){ this.checkTodo(id) }, handleDelete(id,title){ if(confirm("确定删除任务:"+title+"吗?")){ this.deleteTodo(id) } } } } </script> <style scoped> li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; } li label { float: left; cursor: pointer; } li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; } li button { float: right; display: none; margin-top: 3px; } li:before { content: initial; } li:last-child { border-bottom: none; } li:hover { background-color: #eee; } li:hover button{ display: block; } </style>
src/components/MyList.vue
:
<template> <ul class="todo-main"> <MyItem v-for="todo in todos" :key="todo.id" :todo="todo" :checkTodo="checkTodo" :deleteTodo="deleteTodo" /> </ul> </template> <script> import MyItem from './MyItem.vue' export default { name:'MyList', components:{MyItem}, props:['todos','checkTodo','deleteTodo'] } </script> <style scoped> .todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; } .todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } </style>
src/components/MyFooter.vue
:
<template> <div class="todo-footer" v-show="total"> <label> <input type="checkbox" v-model="isAll"/> </label> <span> <span>已完成{{doneTotal}}</span> / 全部{{total}} </span> <button class="btn btn-danger" @click="clearAll">清除已完成任务</button> </div> </template> <script> export default { name:'MyFooter', props:['todos','checkAllTodo','clearAllTodo'], computed:{ doneTotal(){ return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0) }, total(){ return this.todos.length }, isAll:{ get(){ return this.total === this.doneTotal && this.total > 0 }, set(value){ this.checkAllTodo(value) } } }, methods:{ clearAll(){ this.clearAllTodo() } } } </script> <style scoped> .todo-footer { height: 40px; line-height: 40px; padding-left: 6px; margin-top: 5px; } .todo-footer label { display: inline-block; margin-right: 20px; cursor: pointer; } .todo-footer label input { position: relative; top: -1px; vertical-align: middle; margin-right: 5px; } .todo-footer button { float: right; margin-top: 5px; } </style>
src/App.vue
:
<template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <MyHeader :addTodo="addTodo"/> <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/> <MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/> </div> </div> </div> </template> <script> import MyHeader from './components/MyHeader.vue' import MyList from './components/MyList.vue' import MyFooter from './components/MyFooter.vue' export default { name:'App', components: { MyHeader,MyList,MyFooter }, data() { return { todos:[ {id:'001',title:'抽烟',done:false}, {id:'002',title:'喝酒',done:false}, {id:'003',title:'烫头',done:false}, ] } }, methods:{ //添加一个todo addTodo(todoObj){ this.todos.unshift(todoObj) }, //勾选or取消勾选一个todo checkTodo(id){ this.todos.forEach((todo)=>{ if(todo.id === id) todo.done = !todo.done }) }, //删除一个todo deleteTodo(id){ this.todos = this.todos.filter(todo => todo.id !== id) }, //全选or取消勾选 checkAllTodo(done){ this.todos.forEach(todo => todo.done = done) }, //删除已完成的todo clearAllTodo(){ this.todos = this.todos.filter(todo => !todo.done) } } } </script> <style> body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
效果:
图片
总结:
组件化编码流程:
拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突
实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
一个组件在用:放在组件自身即可
一些组件在用:放在他们共同的父组件上(==状态提升==)
实现交互:从绑定事件开始
props
适用于:
父组件 ==> 子组件 通信
子组件 ==> 父组件 通信(要求父组件先给子组件一个函数)
使用v-model
时要切记:v-model
绑定的值不能是props
传过来的值,因为props
是不可以修改的
props
传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但==不推荐这样做==
3.8. WebStorage <!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 > localStorage</title > </head > <body > <h2 > localStorage</h2 > <button onclick ="saveDate()" > 点我保存数据</button > <br /> <button onclick ="readDate()" > 点我读数据</button > <br /> <button onclick ="deleteDate()" > 点我删除数据</button > <br /> <button onclick ="deleteAllDate()" > 点我清空数据</button > <br /> <script > let person = {name :"JOJO" ,age :20 } function saveDate ( ){ localStorage .setItem ('msg' ,'localStorage' ) localStorage .setItem ('person' ,JSON .stringify (person)) } function readDate ( ){ console .log (localStorage .getItem ('msg' )) const person = localStorage .getItem ('person' ) console .log (JSON .parse (person)) } function deleteDate ( ){ localStorage .removeItem ('msg' ) localStorage .removeItem ('person' ) } function deleteAllDate ( ){ localStorage .clear () } </script > </body > </html >
<!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 > sessionStorage</title > </head > <body > <h2 > sessionStorage</h2 > <h2 > 会话存储空间</h2 > <h2 > sessionStorage的特点就是,浏览器关闭就会清空,会话存储本质就是会话,会话结束内存清空</h2 > <button onclick ="saveDate()" > 点我保存数据</button > <br /> <button onclick ="readDate()" > 点我读数据</button > <br /> <button onclick ="deleteDate()" > 点我删除数据</button > <br /> <button onclick ="deleteAllDate()" > 点我清空数据</button > <br /> <script > let person = {name : "杰尼龟" ,age : 25 } function saveDate ( ) { sessionStorage .setItem ('msg' ,"sessionStorage" ) sessionStorage .setItem ('person' ,JSON .stringify (person)) } function readDate ( ) { console .log (sessionStorage .getItem ('msg' )) const person = sessionStorage .getItem ('person' ) console .log (JSON .parse (person)) } function deleteDate ( ) { sessionStorage .removeItem ("msg" ) } function deleteAllDate ( ) { sessionStorage .clear () } </script > </body > </html >
总结:
存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
浏览器端通过Window.sessionStorage
和Window.localStorage
属性来实现本地存储机制
相关API:
xxxStorage.setItem('key', 'value')
:该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值
xxxStorage.getItem('key')
:该方法接受一个键名作为参数,返回键名对应的值
xxxStorage.removeItem('key')
:该方法接受一个键名作为参数,并把该键名从存储中删除
xxxStorage.clear()
:该方法会清空存储中的所有数据
备注:
SessionStorage
存储的内容会随着浏览器窗口关闭而消失
LocalStorage
存储的内容,需要手动清除才会消失
xxxStorage.getItem(xxx)
如果 xxx 对应的 value 获取不到,那么getItem()
的返回值是null
JSON.parse(null)
的结果依然是null
使用本地存储优化Todo-List:
src/App.vue
:
<template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <MyHeader :addTodo="addTodo"/> <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/> <MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/> </div> </div> </div> </template> <script> import MyHeader from './components/MyHeader.vue' import MyList from './components/MyList.vue' import MyFooter from './components/MyFooter.vue' export default { name:'App', components: { MyHeader,MyList,MyFooter }, data() { return { //若localStorage中存有'todos'则从localStorage中取出,否则初始为空数组 todos:JSON.parse(localStorage.getItem('todos')) || [] } }, methods:{ //添加一个todo addTodo(todoObj){ this.todos.unshift(todoObj) }, //勾选or取消勾选一个todo checkTodo(id){ this.todos.forEach((todo)=>{ if(todo.id === id) todo.done = !todo.done }) }, //删除一个todo deleteTodo(id){ this.todos = this.todos.filter(todo => todo.id !== id) }, //全选or取消勾选 checkAllTodo(done){ this.todos.forEach(todo => todo.done = done) }, //删除已完成的todo clearAllTodo(){ this.todos = this.todos.filter(todo => !todo.done) } }, watch:{ todos:{ //由于todos是对象数组,所以必须开启深度监视才能发现数组中对象的变化 deep:true, // deep 开启深度监听 handler(value){ localStorage.setItem('todos',JSON.stringify(value)) } } } } </script> <style> body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
3.9. 自定义事件 3.9.1. 绑定 src/App.vue
:
<template> <div class="app"> <!-- 通过父组件给子组件传递函数类型的props实现子给父传递数据 --> <School :getSchoolName="getSchoolName"/> <!-- 通过父组件给子组件绑定一个自定义事件实现子给父传递数据(第一种写法,使用@或v-on) --> <!-- <Student @jojo="getStudentName"/> --> <!-- 通过父组件给子组件绑定一个自定义事件实现子给父传递数据(第二种写法,使用ref) --> <Student ref="student"/> </div> </template> <script> import Student from './components/Student.vue' import School from './components/School.vue' export default { name:'App', components: { Student,School }, methods:{ getSchoolName(name){ console.log("已收到学校的名称:"+name) }, getStudentName(name){ console.log("已收到学生的姓名:"+name) } }, mounted(){ this.$refs.student.$on('jojo',this.getStudentName) } } </script> <style scoped> .app{ background-color: gray; padding: 5px; } </style>
src/components/Student.vue
:
<template> <div class="student"> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <button @click="sendStudentName">点我传递学生姓名</button> </div> </template> <script> export default { name:'Student', data() { return { name:'JOJO', sex:'男' } }, methods:{ sendStudentName(){ // 触发Student组件实例身上的jojo事件 this.$emit('jojo',this.name) } } } </script> <style scoped> .student{ background-color: chartreuse; padding: 5px; margin-top: 30px; } </style>
src/components/School.vue
:
<template> <div class="school"> <h2>学校姓名:{{name}}</h2> <h2>学校地址:{{address}}</h2> <button @click='sendSchoolName'>把学校名给APP</button> </button> </button> </div> </template> <script> export default { name:'School', props: ['getSchoolName'], // 获取App的getSchoolName的方法 data() { return { name:'尚硅谷', address: "北京" } }, methods: { sendSchoolName() { // 可能会报错 this.getShoolName(this.name) } } } </script> <style scoped> .school{ background-color: skyblue; padding: 5px; margin-top: 30px; } </style>
效果:
3.9.2. 解绑 src/App.vue
:
<template> <div class="app"> <Student @jojo="getStudentName"/> </div> </template> <script> import Student from './components/Student.vue' export default { name:'App', components: { Student }, methods:{ getStudentName(name){ console.log("已收到学生的姓名:"+name) } } } </script> <style scoped> .app{ background-color: gray; padding: 5px; } </style>
src/components/Student.vue
:
<template> <div class="student"> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <button @click="sendStudentName">点我传递学生姓名</button> <button @click="unbind">解绑自定义事件</button> </div> </template> <script> export default { name:'Student', data() { return { name:'JOJO', sex:'男' } }, methods:{ sendStudentName(){ this.$emit('jojo',this.name) }, unbind(){ // 解绑一个自定义事件 // this.$off('jojo') // 解绑多个自定义事件 // this.$off(['jojo']) // 解绑所有自定义事件 this.$off() } } } </script> <style scoped> .student{ background-color: chartreuse; padding: 5px; margin-top: 30px; } </style>
效果:
总结:
组件的自定义事件:
一种组件间通信的方式,适用于:==子组件 > 父组件
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)
绑定自定义事件:
第一种方式,在父组件中:<Demo @atguigu="test"/>
或 <Demo v-on:atguigu="test"/>
第二种方式,在父组件中:
<Demo ref="demo" /> ... mounted ( ){ this .$refs .demo .$on('atguigu' ,data) }
若想让自定义事件只能触发一次,可以使用once
修饰符,或$once
方法
触发自定义事件:this.$emit('atguigu',数据)
解绑自定义事件:this.$off('atguigu')
组件上也可以绑定原生DOM事件,需要使用native
修饰符
注意:通过this.$refs.xxx.$on('atguigu',回调)
绑定自定义事件时,回调==要么配置在methods中==,要么==用箭头函数==,否则this指向会出问题!
使用自定义事件优化Todo-List:
src/App.vue
:
<template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <MyHeader @addTodo="addTodo"/> <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/> <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/> </div> </div> </div> </template> <script> import MyHeader from './components/MyHeader.vue' import MyList from './components/MyList.vue' import MyFooter from './components/MyFooter.vue' export default { name:'App', components: { MyHeader,MyList,MyFooter }, data() { return { todos:JSON.parse(localStorage.getItem('todos')) || [] } }, methods:{ //添加一个todo addTodo(todoObj){ this.todos.unshift(todoObj) }, //勾选or取消勾选一个todo checkTodo(id){ this.todos.forEach((todo)=>{ if(todo.id === id) todo.done = !todo.done }) }, //删除一个todo deleteTodo(id){ this.todos = this.todos.filter(todo => todo.id !== id) }, //全选or取消勾选 checkAllTodo(done){ this.todos.forEach(todo => todo.done = done) }, //删除已完成的todo clearAllTodo(){ this.todos = this.todos.filter(todo => !todo.done) } }, watch:{ todos:{ deep:true, handler(value){ localStorage.setItem('todos',JSON.stringify(value)) } } } } </script> <style> body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
src/components/MyHeader.vue
:
<template> <div class="todo-header"> <input type="text" placeholder="请输入你的任务名称,按回车键确认" @keydown.enter="add" v-model="title"/> </div> </template> <script> import {nanoid} from 'nanoid' export default { name:'MyHeader', data() { return { title:'' } }, methods:{ add(){ if(!this.title.trim()) return const todoObj = {id:nanoid(),title:this.title,done:false} this.$emit('addTodo',todoObj) this.title = '' } } } </script> <style scoped> /*header*/ .todo-header input { width: 560px; height: 28px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px; padding: 4px 7px; } .todo-header input:focus { outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); } </style>
src/components/MyFooter
:
<template> <div class="todo-footer" v-show="total"> <label> <input type="checkbox" v-model="isAll"/> </label> <span> <span>已完成{{doneTotal}}</span> / 全部{{total}} </span> <button class="btn btn-danger" @click="clearAll">清除已完成任务</button> </div> </template> <script> export default { name:'MyFooter', props:['todos'], computed:{ doneTotal(){ return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0) }, total(){ return this.todos.length }, isAll:{ get(){ return this.total === this.doneTotal && this.total > 0 }, set(value){ this.$emit('checkAllTodo',value) } } }, methods:{ clearAll(){ this.$emit('clearAllTodo') } } } </script> <style scoped> .todo-footer { height: 40px; line-height: 40px; padding-left: 6px; margin-top: 5px; } .todo-footer label { display: inline-block; margin-right: 20px; cursor: pointer; } .todo-footer label input { position: relative; top: -1px; vertical-align: middle; margin-right: 5px; } .todo-footer button { float: right; margin-top: 5px; } </style>
3.10. 全局事件总线
全局事件总线是一种可以在任意组件间通信的方式,本质上就是一个对象。它必须满足以下条件:1. 所有的组件对象都必须能看见他 2. 这个对象必须能够使用$on
、$emit
和$off
方法去绑定、触发和解绑事件
src/main.js
:
import Vue from 'vue' import App from './App.vue' Vue .config .productionTip = false new Vue ({ el :'#app' , render : h => h (App ), beforeCreate ( ) { Vue .prototype .$bus = this } })
src/App.vue
:
<template> <div class="app"> <School/> <Student/> </div> </template> <script> import Student from './components/Student' import School from './components/School' export default { name:'App', components:{School,Student} } </script> <style scoped> .app{ background-color: gray; padding: 5px; } </style>
src/components/School.vue
:
<template> <div class="school"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> </template> <script> export default { name:'School', data() { return { name:'尚硅谷', address:'北京', } }, methods:{ // 声明 demo 函数 demo(data) { console.log('我是School组件,收到了数据:',data) } }, // mounted 钩子函数,主要用于初始化操作,比如:开启定时器,发送网络请求,绑定事件 mounted() { // 把 this.demo 绑定到bus上 this.$bus.$on('demo',this.demo) }, // beforeDestroy 收尾操作 beforeDestroy() { // beforeDestroy 销毁bus上的demo函数 this.$bus.$off('demo') }, } </script> <style scoped> .school{ background-color: skyblue; padding: 5px; } </style>
src/components/Student.vue
:
<template> <div class="student"> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <button @click="sendStudentName">把学生名给School组件</button> </div> </template> <script> export default { name:'Student', data() { return { name:'张三', sex:'男' } }, methods: { sendStudentName(){ // 调用 bus 上的demo 函数,并把name传入 this.$bus.$emit('demo',this.name) } } } </script> <style scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; } </style>
效果:
总结:
全局事件总线(GlobalEventBus):
一种组件间通信的方式,适用于任意组件间通信
安装全局事件总线:
new Vue ({ ... beforeCreate ( ) { Vue .prototype .$bus = this }, ... })
使用事件总线:
接收数据:A组件想接收数据,则在A组件中给$bus
绑定自定义事件,事件的回调留在A组件自身
export default { methods ( ){ demo (data ){...} } ... mounted ( ) { this .$bus .$on('xxx' ,this .demo ) } }
提供数据:this.$bus.$emit('xxx',data)
最好在beforeDestroy
钩子中,用$off
去==解绑当前组件所用到的事件==.
使用自定义事件优化Todo-List:
src/mian.js
:
import Vue from 'vue' import App from './App.vue' Vue .config .productionTip = false new Vue ({ el :"#app" , render : h => h (App ), beforeCreate ( ) { Vue .prototype .$bus = this } })
src/components/App.vue
:
<template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <MyHeader @addTodo="addTodo"/> <MyList :todos="todos"/> <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/> </div> </div> </div> </template> <script> import MyHeader from './components/MyHeader.vue' import MyList from './components/MyList.vue' import MyFooter from './components/MyFooter.vue' export default { name:'App', components: { MyHeader,MyList,MyFooter }, data() { return { todos:JSON.parse(localStorage.getItem('todos')) || [] } }, methods:{ //添加一个todo addTodo(todoObj){ this.todos.unshift(todoObj) }, //勾选or取消勾选一个todo checkTodo(id){ this.todos.forEach((todo)=>{ if(todo.id === id) todo.done = !todo.done }) }, //删除一个todo deleteTodo(id){ this.todos = this.todos.filter(todo => todo.id !== id) }, //全选or取消勾选 checkAllTodo(done){ this.todos.forEach(todo => todo.done = done) }, //删除已完成的todo clearAllTodo(){ this.todos = this.todos.filter(todo => !todo.done) } }, watch:{ todos:{ deep:true, handler(value){ localStorage.setItem('todos',JSON.stringify(value)) } } }, mounted(){ this.$bus.$on('checkTodo',this.checkTodo) this.$bus.$on('deleteTodo',this.deleteTodo) }, beforeDestroy(){ this.$bus.$off(['checkTodo','deleteTodo']) } } </script> <style> body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
src/components/MyItem.vue
:
<template> <li> <label> <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/> <span>{{todo.title}}</span> </label> <button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button> </li> </template> <script> export default { name:'MyItem', props:['todo'], methods:{ handleCheck(id){ this.$bus.$emit('checkTodo',id) }, handleDelete(id,title){ if(confirm("确定删除任务:"+title+"吗?")){ this.$bus.$emit('deleteTodo',id) } } } } </script> <style scoped> li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; } li label { float: left; cursor: pointer; } li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; } li button { float: right; display: none; margin-top: 3px; } li:before { content: initial; } li:last-child { border-bottom: none; } li:hover { background-color: #eee; } li:hover button{ display: block; } </style>
3.11. 消息的订阅与发布 src/components/School.vue
:
<template> <div class="school"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> </template> <script> import pubsub from 'pubsub-js' export default { name:'School', data() { return { name:'尚硅谷', address:'北京', } }, methods:{ demo(msgName,data) { console.log('我是School组件,收到了数据:',data) } }, mounted() { this.pubId = pubsub.subscribe('demo',this.demo) //订阅消息 }, beforeDestroy() { pubsub.unsubscribe(this.pubId) //取消订阅 } } </script> <style scoped> .school{ background-color: skyblue; padding: 5px; } </style>
src/components/Student.vue
:
<template> <div class="student"> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <button @click="sendStudentName">把学生名给School组件</button> </div> </template> <script> import pubsub from 'pubsub-js' export default { name:'Student', data() { return { name:'JOJO', sex:'男', } }, methods: { sendStudentName(){ pubsub.publish('demo',this.name) //发布消息 } } } </script> <style scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; } </style>
效果:
总结:
消息订阅与发布(pubsub):
消息订阅与发布是一种组件间通信的方式,适用于==任意组件间通信==
使用步骤:
安装pubsub:npm i pubsub-js
引入:import pubsub from 'pubsub-js'
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
export default { methods ( ){ demo (data ){...} } ... mounted ( ) { this .pid = pubsub.subscribe ('xxx' ,this .demo ) } }
提供数据:pubsub.publish('xxx',data)
最好在beforeDestroy
钩子中,使用pubsub.unsubscribe(pid)
取消订阅
使用消息的订阅与发布优化Todo-List:
src/App.vue
:
<template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <MyHeader @addTodo="addTodo"/> <MyList :todos="todos"/> <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/> </div> </div> </div> </template> <script> import pubsub from 'pubsub-js' import MyHeader from './components/MyHeader.vue' import MyList from './components/MyList.vue' import MyFooter from './components/MyFooter.vue' export default { name:'App', components: { MyHeader,MyList,MyFooter }, data() { return { todos:JSON.parse(localStorage.getItem('todos')) || [] } }, methods:{ //添加一个todo addTodo(todoObj){ this.todos.unshift(todoObj) }, //勾选or取消勾选一个todo checkTodo(_,id){ this.todos.forEach((todo)=>{ if(todo.id === id) todo.done = !todo.done }) }, //删除一个todo deleteTodo(id){ this.todos = this.todos.filter(todo => todo.id !== id) }, //全选or取消勾选 checkAllTodo(done){ this.todos.forEach(todo => todo.done = done) }, //删除已完成的todo clearAllTodo(){ this.todos = this.todos.filter(todo => !todo.done) } }, watch:{ todos:{ deep:true, handler(value){ localStorage.setItem('todos',JSON.stringify(value)) } } }, mounted(){ this.pubId = pubsub.subscribe('checkTodo',this.checkTodo) this.$bus.$on('deleteTodo',this.deleteTodo) }, beforeDestroy(){ pubsub.unsubscribe(this.pubId) this.$bus.$off('deleteTodo') } } </script> <style> body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
src/components/myItem.vue
:
<template> <li> <label> <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/> <span>{{todo.title}}</span> </label> <button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button> </li> </template> <script> import pubsub from 'pubsub-js' export default { name:'MyItem', props:['todo'], methods:{ handleCheck(id){ pubsub.publish('checkTodo',id) }, handleDelete(id,title){ if(confirm("确定删除任务:"+title+"吗?")){ this.$bus.$emit('deleteTodo',id) } } } } </script> <style scoped> li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; } li label { float: left; cursor: pointer; } li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; } li button { float: right; display: none; margin-top: 3px; } li:before { content: initial; } li:last-child { border-bottom: none; } li:hover { background-color: #eee; } li:hover button{ display: block; } </style>
3.12. $nextTick
$nextTick(回调函数)
可以将回调延迟到下次 DOM 更新循环之后执行
使用$nextTick优化Todo-List:
src/App.vue
:
<template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <MyHeader @addTodo="addTodo"/> <MyList :todos="todos"/> <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/> </div> </div> </div> </template> <script> import pubsub from 'pubsub-js' import MyHeader from './components/MyHeader.vue' import MyList from './components/MyList.vue' import MyFooter from './components/MyFooter.vue' export default { name:'App', components: { MyHeader,MyList,MyFooter }, data() { return { todos:JSON.parse(localStorage.getItem('todos')) || [] } }, methods:{ //添加一个todo addTodo(todoObj){ this.todos.unshift(todoObj) }, //勾选or取消勾选一个todo checkTodo(_,id){ this.todos.forEach((todo)=>{ if(todo.id === id) todo.done = !todo.done }) }, //删除一个todo deleteTodo(id){ this.todos = this.todos.filter(todo => todo.id !== id) }, //更新一个todo updateTodo(id,title){ this.todos.forEach((todo)=>{ if(todo.id === id) todo.title = title }) }, //全选or取消勾选 checkAllTodo(done){ this.todos.forEach(todo => todo.done = done) }, //删除已完成的todo clearAllTodo(){ this.todos = this.todos.filter(todo => !todo.done) } }, watch:{ todos:{ deep:true, handler(value){ localStorage.setItem('todos',JSON.stringify(value)) } } }, mounted(){ this.pubId = pubsub.subscribe('checkTodo',this.checkTodo) this.$bus.$on('deleteTodo',this.deleteTodo) this.$bus.$on('updateTodo',this.updateTodo) }, beforeDestroy(){ pubsub.unsubscribe(this.pubId) this.$bus.$off('deleteTodo') this.$bus.$off('updateTodo') } } </script> <style> body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #e04e49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn-info { color: #fff; background-color: rgb(50, 129, 233); border: 1px solid rgb(1, 47, 212); margin-right: 5px; } .btn-info:hover { color: #fff; background-color: rgb(1, 47, 212); } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
src/components/MyItem.vue
:
<template> <li> <label> <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/> <span v-show="!todo.isEdit">{{todo.title}}</span> <input type="text" v-show="todo.isEdit" :value="todo.title" @blur="handleBlur(todo,$event)" ref="inputTitle"> </label> <button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button> <button class="btn btn-info" v-show="!todo.isEdit" @click="handleEdit(todo)">编辑</button> </li> </template> <script> import pubsub from 'pubsub-js' export default { name:'MyItem', props:['todo'], methods:{ handleCheck(id){ pubsub.publish('checkTodo',id) }, handleDelete(id,title){ if(confirm("确定删除任务:"+title+"吗?")){ this.$bus.$emit('deleteTodo',id) } }, handleEdit(todo){ // 如果todo自身有isEdit属性就将isEdit改成true if(Object.prototype.hasOwnProperty.call(todo,'isEdit')){ todo.isEdit = true }else{ // 如果没有就向todo中添加一个响应式的isEdit属性并设为true this.$set(todo,'isEdit',true) } // 当Vue重新编译模板之后执行$nextTick()中的回调函数 this.$nextTick(function(){ // 使input框获取焦点 this.$refs.inputTitle.focus() }) }, // 当input框失去焦点时更新 handleBlur(todo,event){ todo.isEdit = false if(!event.target.value.trim()) return alert('输入不能为空!') this.$bus.$emit('updateTodo',todo.id,event.target.value) } } } </script> <style scoped> li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; } li label { float: left; cursor: pointer; } li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; } li button { float: right; display: none; margin-top: 3px; } li:before { content: initial; } li:last-child { border-bottom: none; } li:hover { background-color: #eee; } li:hover button{ display: block; } </style>
Todo-List最终效果:
总结:
$nextTick:
语法:this.$nextTick(回调函数)
作用:在下一次 DOM 更新结束后执行其指定的回调
什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
3.13. 过渡与动画 src/App.vue
:
<template> <div id="root"> <MyAnimation/> <MyTransition/> <MyTransitionGroup/> <ThirdPartAnimation/> </div> </template> <script> import MyAnimation from './components/MyAnimation.vue' import MyTransition from './components/MyTransition.vue' import MyTransitionGroup from './components/MyTransitionGroup.vue' import ThirdPartAnimation from './components/ThirdPartAnimation.vue' export default { name:'App', components: { MyAnimation,MyTransition,MyTransitionGroup,ThirdPartAnimation }, } </script>
src/components/MyAnimation
:
<template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition name="jojo" appear> <h1 v-show="isShow">你好啊!</h1> </transition> </div> </template> <script> export default { name:'MyTitle', data() { return { isShow:true } } } </script> <style scoped> h1{ background-color: orange; } .jojo-enter-active{ animation: jojo 0.5s linear; } .jojo-leave-active{ animation: jojo 0.5s linear reverse; } @keyframes jojo { from{ transform: translateX(-100%); } to{ transform: translateX(0px); } } </style>
src/components/MyTransition.vue
:
<template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition name="jojo" appear> <h1 v-show="isShow">你好啊!</h1> </transition> </div> </template> <script> export default { name:'MyTitle', data() { return { isShow:true } } } </script> <style scoped> h1{ background-color: orange; } .jojo-enter,.jojo-leave-to{ transform: translateX(-100%); } .jojo-enter-to,.jojo-leave{ transform: translateX(0); } .jojo-enter-active,.jojo-leave-active{ transition: 0.5s linear; } </style>
src/components/MyTransitionGroup.vue
:
<template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition-group name="jojo" appear> <h1 v-show="isShow" key="1">你好啊!</h1> <h1 v-show="!isShow" key="2">大笨蛋</h1> </transition-group> </div> </template> <script> export default { name:'MyTitle', data() { return { isShow:true } } } </script> · <style scoped> h1{ background-color: orange; } .jojo-enter,.jojo-leave-to{ transform: translateX(-100%); } .jojo-enter-to,.jojo-leave{ transform: translateX(0); } .jojo-enter-active,.jojo-leave-active{ transition: 0.5s linear; } </style>
src/components/ThirdPartAnimation
:
<template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition-group appear name="animate__animated animate__bounce" enter-active-class="animate__backInUp" leave-active-class="animate__backOutUp" > <h1 v-show="isShow" key="1">你好啊!</h1> <h1 v-show="!isShow" key="2">大笨蛋</h1> </transition-group> </div> </template> <script> import 'animate.css' export default { name:'MyTitle', data() { return { isShow:true } } } </script> <style scoped> h1{ background-color: orange; } </style>
效果:
总结:
Vue封装的过度与动画:
作用:在插入、更新或移除 DOM元素时,==在合适的时候给元素添加样式类名==
写法:
准备好样式:
元素进入的样式:
v-enter
:进入的起点
v-enter-active
:进入过程中
v-enter-to
:进入的终点
元素离开的样式:
v-leave
:离开的起点
v-leave-active
:离开过程中
v-leave-to
:离开的终点
使用<transition>
包裹要过度的元素,并配置name属性:
<transition name="hello"> <h1 v-show="isShow">你好啊!</h1> </transition>
备注:若有多个元素需要过度,则需要使用:<transition-group>
,且每个元素都要指定key
值
可以借助第三方库,npm官网上的 animation.css
App.js
中直接引入import animation.css
使用动画优化Todo-List:
src/components/MyList.vue
:
<template> <ul class="todo-main"> <transition-group name="todo" appear> <MyItem v-for="todo in todos" :key="todo.id" :todo="todo"/> </transition-group> </ul> </template> <script> import MyItem from './MyItem.vue' export default { name:'MyList', components:{MyItem}, props:['todos'] } </script> <style scoped> .todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; } .todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } .todo-enter-active{ animation: todo 0.5s linear; } .todo-leave-active{ animation: todo 0.5s linear reverse; } @keyframes todo { from{ transform: translateX(-100%); } to{ transform: translateX(0px); } } </style>