赞
踩
动态构建用户界面的渐进式JavaScript框架
作者:尤雨溪
遵循MVVM模式
编码简洁,体积小,运行效率高,适合移动/PC端开发
它本身只关注UI,可以引入其它第三方库开发项目
借鉴 Angular 的模板和数据绑定技术
借鉴 React 的组件化和虚拟DOM技术
<!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> <!-- 引入Vue --> <script src="../js/vue.js"></script> </head> <body> <!-- 准备好一个容器 --> <div id="root"> <h1>Hello!{{name}}!</h1> </div> <script> Vue.config.productionTip = false // 阻止vue在启动时生成生产提示 new Vue({ el:'#root', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串 data:{ //data用于存储数据,数据共el所指定的容器去使用 name:'JOJO' } }) </script> </body> </html>
效果:
注意:
{{xxx}}
中的xxx要写js表达式,且xxx可以自动读取到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>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模板语法包括两大类:
插值语法:
指令语法:
<a v-bind:href="xxx">
或简写为<a :href="xxx">
,xxx同样要写js表达式,且可以直接读取到data中的所有属性v-???
,此处我们只是拿v-bind
举个例子<!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种数据绑定的方式:
备注:
<!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 //el的两种写法: // const vm = new Vue({ // // el:'#root', //第一种写法 // data:{ // name:'JOJO' // } // }) // vm.$mount('#root')//第二种写法 //data的两种写法: new Vue({ el:'#root', //data的第一种写法:对象式 // data:{ // name:'JOJO' // } //data的第二种写法:函数式 data(){ return{ name:'JOJO' } } }) </script> </body> </html>
总结:
el有2种写法:
vm.$mount('#root')
指定el的值data有2种写法:
由Vue管理的函数,一定不要写箭头函数,否则this就不再是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>
效果:
总结:
总结:
<!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是事件名@click="demo
和@click="demo($event)"
效果一致,但后者可以传参<!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> <!-- 只有event.target是当前操作的元素时才触发事件 --> <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中的事件修饰符:
修饰符可以连续写,比如可以这么用:
@click.prevent.stop="showInfo"
<!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中常用的按键别名:
系统修饰键(用法特殊):ctrl、alt、shift、meta
可以使用keyCode去指定具体的按键,比如:@keydown.13="showInfo"
,但不推荐这样使用
Vue.config.keyCodes.自定义键名 = 键码
,可以自定义按键别名
<!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实现相比,内部有缓存机制(复用),效率更高,调试方便
备注:
new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三'
},
computed:{
fullName(){
return this.firstName + '-' + this.lastName
}
}
})
<!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调用一下 //handler什么时候调用?当isHot发生改变时 handler(newValue,oldValue){ console.log('isHot被修改了',newValue,oldValue) } } } }) </script> </body> </html>
效果:
总结:
监视属性watch:
vm.$watch
监视vm.$watch('isHot',{
immediate:true,
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
})
<!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改变了') } } //监视多级结构中某个属性的变化 /* 'numbers.a':{ handler(){ console.log('a被改变了') } } */ } }) </script> </body> </html>
效果:
总结:
如果监视属性除了handler没有其他配置项的话,可以进行简写。
<script type="text/javascript"> Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 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>
使用计算属性:
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 } } })
总结:
computed和watch之间的区别:
两个重要的小原则:
<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"> <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 --> <div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br/><br/> <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 --> <div class="basic" :class="classArr">{{name}}</div> <br/><br/> <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 --> <div class="basic" :class="classObj">{{name}}</div> <br/><br/> <!-- 绑定style样式--对象写法 --> <div class="basic" :style="styleObj">{{name}}</div> <br/><br/> <!-- 绑定style样式--数组写法 --> <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样式:
字符串写法适用于:类名不确定,要动态获取
对象写法适用于:要绑定多个样式,个数不确定,名字也不确定
数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用
:style="{fontSize: xxx}"
其中xxx是动态值:style="[a,b]"
其中a、b是样式对象<!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:
1. v-if="表达式"
2. v-else-if="表达式"
3. v-else
适用于:切换频率较低的场景
特点:不展示的DOM元素直接被移除
注意:v-if可以和v-else-if、v-else一起使用,但要求结构不能被打断
v-show:
使用
v-if
的时,元素可能无法获取到,而使用v-show
一定可以获取到
<!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,也可以是遍历对象的唯一标识<!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相同的key:创建新的真实DOM,随后渲染到到页面
用index作为key可能会引发的问题:
开发中如何选择key?
<!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>
效果:
<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,//0代表原顺序,1代表升序,3代表降序 }, 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>
效果:
<!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 //阻止 vue 在启动时生成生产提示。 const vm = new Vue({ el:'#root', data:{ student:{ name:'tom', age:18, hobby:['抽烟','喝酒','烫头'], friends:[ {name:'jerry',age:35}, {name:'tony',age:36} ] } }, methods: { addSex(){ //Vue.set(this.student,'sex','男') 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.set(target,propertyName/index,value)
vm.$set(target,propertyName/index,value)
如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
在Vue修改数组中的某个元素一定要用如下方法:
push()
、pop()
、shift()
、unshift()
、splice()
、sort()
、reverse()
Vue.set()
或 vm.$set()
Vue.set()
和 vm.$set()
不能给vm 或 vm的根数据对象(data等) 添加属性<!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>
效果:
总结:
收集表单数据:
<input type="text"/>
,则v-model收集的是value值,用户输入的内容就是value值<input type="radio"/>
,则v-model收集的是value值,且要给标签配置value属性<input type="checkbox"/>
v-model
的三个修饰符:<!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 | 过滤器名”
备注:
<!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}}则不会。
<!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 //阻止 vue 在启动时生成生产提示。 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
有安全性问题!!!
1 . 在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击
2. 一定要在可信的内容上使用v-html
,永远不要用在用户提交的内容上!!!
<!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
指令(没有值):
v-cloak
属性v-cloak
可以解决网速慢时页面展示出{{xxx}}
的问题<!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所在结构的更新,可以用于优化性能
<!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
指令:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>自定义指令</title> <script type="text/javascript" src="../js/vue.js"></script> </head> <!-- 需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。 需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。 --> <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函数何时会被调用?1.指令与元素成功绑定时(一上来) 2.指令所在的模板被重新解析时 big(element,binding){ console.log('big',this) //注意此处的this是window 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:{指令名:配置对象} })
new Vue({
directives:{指令名:回调函数} })
全局指令:
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
}
})
配置对象中常用的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
}
}
})
<!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>
效果:
总结:
生命周期:
<!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', // template:` // <div> // <h2>当前的n值是:{{n}}</h2> // <button @click="add">点我n+1</button> // </div> // `, 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>
效果:
<!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操作数据,因为即便操作数据,也不会再触发更新流程了
当应用中的 js 都以模块来编写的,那这个应用就是一个模块化的应用
当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用
<!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>
效果:
总结:
Vue中使用组件的三大步骤:
如何定义一个组件?
使用Vue.extend(options)
创建,其中options
和new Vue(options)
时传入的options
几乎一样,但也有点区别:
el不要写,为什么?
最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
data必须写成函数,为什么?
避免组件被复用时,数据存在引用关系
如何注册组件?
new Vue
的时候传入components
选项Vue.component('组件名',组件)
编写组件标签:<school></school>
<!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>
效果:
总结:
关于组件名:
一个单词组成:
多个单词组成:
备注:
关于组件标签:
一个简写方式:const school = Vue.extend(options)
可简写为:const school = options
<!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>
效果:
关于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
<!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>
效果:
总结:
<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>
<template> <div> <h2>学生姓名:{{name}}</h2> <h2>学生年龄:{{age}}</h2> </div> </template> <script> export default { name:'Student', data() { return { name:'JOJO', age:20 } }, } </script>
<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>
import App from './App.vue'
new Vue({
template:`<App></App>`,
el:'#root',
components:{App}
})
<!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>
npm config set registry http://registry.npm.taobao.org
npm install -g @vue/cli
vue create xxxx
npm run serve
Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置,请执行:
vue inspect > output.js
脚手架文件结构:
.文件目录 ├── 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"> <!-- 针对IE浏览器的特殊配置,含义是让IE浏览器以最高渲染级别渲染页面 --> <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>
效果:
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
el:'#app',
// 简写形式
render: h => h(App),
// 完整形式
// render(createElement){
// return createElement(App)
// }
})
总结:
关于不同版本的函数:
vue.js
与 vue.runtime.xxx.js
的区别:
vue.js
是完整版的 Vue,包含:核心功能+模板解析器vue.runtime.xxx.js
是运行版的 Vue,只包含核心功能,没有模板解析器因为 vue.runtime.xxx.js
没有模板解析器,所以不能使用 template
配置项,需要使用 render函数接收到的createElement
函数去指定具体内容
vue.config.js
是一个可选的配置文件,如果项目的(和 package.json
同级的)根目录中存在这个文件,那么它会被 @vue/cli-service
自动加载vue.config.js
可以对脚手架进行个性化定制,详见配置参考 | Vue CLImodule.exports = {
pages: {
index: {
// 入口
entry: 'src/index/main.js'
}
},
// 关闭语法检查
lineOnSave:false
}
<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
属性:
<h1 ref="xxx"></h1>
或 <School ref="xxx"></School>
this.$refs.xxx
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
:
<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中的数据
局部混入:
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) // => { message: "goodbye", foo: "abc", bar: "def" } } })
var mixin = {
created () {
console.log('混入对象的钩子被调用')
}
}
new Vue({
mixins: [mixin],
created () {
console.log('组件钩子被调用')
}
})
// => "混入对象的钩子被调用"
// => "组件钩子被调用"
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原型上添加一个方法(vm和vc就都能用了) 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:'博客', 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) {
// 1. 添加全局过滤器
Vue.filter(....)
// 2. 添加全局指令
Vue.directive(....)
// 3. 配置全局混入
Vue.mixin(....)
// 4. 添加实例方法
Vue.prototype.$myMethod = function () {...}
Vue.prototype.$myProperty = xxxx
}
Vue.use(plugin)
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
中使用
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>
效果:
总结:
组件化编码流程:
props
适用于:
使用v-model
时要切记:v-model
绑定的值不能是props
传过来的值,因为props
是不可以修改的
props
传过来的若是对象类型的值,修改对象中的属性时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>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> <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(){ 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') sessionStorage.removeItem('person') } 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()
的返回值是nullJSON.parse(null)
的结果依然是nullsrc/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, 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/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(){ this.$emit('jojo',this.name) } } } </script> <style scoped> .student{ background-color: chartreuse; padding: 5px; margin-top: 30px; } </style>
效果:
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 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>
全局事件总线是一种可以在任意组件间通信的方式,本质上就是一个对象。它必须满足以下条件: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(data) { console.log('我是School组件,收到了数据:',data) } }, mounted() { this.$bus.$on('demo',this.demo) }, beforeDestroy() { 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(){ 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 //安装全局事件总线,$bus就是当前应用的vm
},
...
})
使用事件总线:
$bus
绑定自定义事件,事件的回调留在A组件自身export default {
methods(){
demo(data){...}
}
...
mounted() {
this.$bus.$on('xxx',this.demo)
}
}
this.$bus.$emit('xxx',data)
4.最好在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>
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>
$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:
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元素时,在合适的时候给元素添加样式类名
图示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F7tX8Utw-1648820283412)(https://cn.vuejs.org/images/transition.png)]
写法:
准备好样式:
元素进入的样式:
元素离开的样式:
使用<transition>
包裹要过度的元素,并配置name属性:
<transition name="hello">
<h1 v-show="isShow">你好啊!</h1>
</transition>
<transition-group>
,且每个元素都要指定key
值使用动画优化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>
本案例需要下载axios库:
npm install axios
vue.config.js
:
module.exports = { pages: { index: { entry: 'src/main.js', }, }, lintOnSave:false, // 开启代理服务器(方式一) // devServer: { // proxy:'http://localhost:5000' // } //开启代理服务器(方式二) devServer: { proxy: { '/jojo': { target: 'http://localhost:5000', pathRewrite:{'^/jojo':''}, // ws: true, //用于支持websocket,默认值为true // changeOrigin: true //用于控制请求头中的host值,默认值为true }, '/atguigu': { target: 'http://localhost:5001', pathRewrite:{'^/atguigu':''}, // ws: true, //用于支持websocket,默认值为true // changeOrigin: true //用于控制请求头中的host值,默认值为true } } } }
src/App.vue
:
<template> <div id="root"> <button @click="getStudents">获取学生信息</button><br/> <button @click="getCars">获取汽车信息</button> </div> </template> <script> import axios from 'axios' export default { name:'App', methods: { getStudents(){ axios.get('http://localhost:8080/jojo/students').then( response => { console.log('请求成功了',response.data) }, error => { console.log('请求失败了',error.message) } ) }, getCars(){ axios.get('http://localhost:8080/atguigu/cars').then( response => { console.log('请求成功了',response.data) }, error => { console.log('请求失败了',error.message) } ) } } } </script>
效果:
总结:
vue脚手架配置代理服务器:
devServer:{
proxy:"http://localhost:5000"
}
说明:
devServer: { proxy: { '/api1': { // 匹配所有以 '/api1'开头的请求路径 target: 'http://localhost:5000',// 代理目标的基础路径 changeOrigin: true, pathRewrite: {'^/api1': ''} }, '/api2': { // 匹配所有以 '/api2'开头的请求路径 target: 'http://localhost:5001',// 代理目标的基础路径 changeOrigin: true, pathRewrite: {'^/api2': ''} } } } // changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000 // changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
说明:
public/index.html
:
<!DOCTYPE html> <html lang=""> <head> <meta charset="UTF-8"> <!-- 针对IE浏览器的特殊配置,含义是让IE浏览器以最高渲染级别渲染页面 --> <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"> <!-- 引入bootstrap样式 --> <link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css"> <!-- 配置网页标题 --> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> <!-- 容器 --> <div id="app"></div> </body> </html>
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="container"> <Search/> <List/> </div> </template> <script> import Search from './components/Search.vue' import List from './components/List.vue' export default { name:'App', components:{Search,List}, } </script>
src/components/Search.vue
:
<template> <section class="jumbotron"> <h3 class="jumbotron-heading">Search Github Users</h3> <div> <input type="text" placeholder="enter the name you search" v-model="keyWord"/> <button @click="getUsers">Search</button> </div> </section> </template> <script> import axios from 'axios' export default { name:'Search', data() { return { keyWord:'' } }, methods: { getUsers(){ //请求前更新List的数据 this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false}) axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then( response => { console.log('请求成功了') //请求成功后更新List的数据 this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items}) }, error => { //请求后更新List的数据 this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]}) } ) } } } </script>
src/components/List.vue
:
<template> <div class="row"> <!-- 展示用户列表 --> <div class="card" v-show="info.users.length" v-for="user in info.users" :key="user.id"> <a :href="user.html_url" target="_blank"> <img :src="user.avatar_url" style='width: 100px'/> </a> <h4 class="card-title">{{user.login}}</h4> </div> <!-- 展示欢迎词 --> <h1 v-show="info.isFirst">欢迎使用!</h1> <!-- 展示加载中 --> <h1 v-show="info.isLoading">加载中...</h1> <!-- 展示错误信息 --> <h1 v-show="info.errMsg">{{errMsg}}</h1> </div> </template> <script> export default { name:'List', data() { return { info:{ isFirst:true, isLoading:false, errMsg:'', users:[] } } }, mounted(){ this.$bus.$on('updateListData',(dataObj)=>{ //动态合并两个对象的属性 this.info = {...this.info,...dataObj} }) }, beforeDestroy(){ this.$bus.$off('updateListData') } } </script> <style scoped> .album { min-height: 50rem; /* Can be removed; just added for demo purposes */ padding-top: 3rem; padding-bottom: 3rem; background-color: #f7f7f7; } .card { float: left; width: 33.333%; padding: .75rem; margin-bottom: 2rem; border: 1px solid #efefef; text-align: center; } .card > img { margin-bottom: .75rem; border-radius: 100px; } .card-text { font-size: 85%; } </style>
效果:
下载 vue-resource库:
npm i vue-resource
src/main.js
:
import Vue from 'vue'
import App from './App.vue'
import vueResource from 'vue-resource'
Vue.config.productionTip = false
Vue.use(vueResource)
new Vue({
el:"#app",
render: h => h(App),
beforeCreate(){
Vue.prototype.$bus = this
}
})
src/App.vue
:
<template> <div class="container"> <Search/> <List/> </div> </template> <script> import Search from './components/Search.vue' import List from './components/List.vue' export default { name:'App', components:{Search,List}, } </script>
src/components/Search.vue
:
<template> <section class="jumbotron"> <h3 class="jumbotron-heading">Search Github Users</h3> <div> <input type="text" placeholder="enter the name you search" v-model="keyWord"/> <button @click="getUsers">Search</button> </div> </section> </template> <script> export default { name:'Search', data() { return { keyWord:'' } }, methods: { getUsers(){ //请求前更新List的数据 this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false}) this.$http.get(`https://api.github.com/search/users?q=${this.keyWord}`).then( response => { console.log('请求成功了') //请求成功后更新List的数据 this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items}) }, error => { //请求后更新List的数据 this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]}) } ) } } } </script>
src/components/List.vue
:
<template> <div class="row"> <!-- 展示用户列表 --> <div class="card" v-show="info.users.length" v-for="user in info.users" :key="user.id"> <a :href="user.html_url" target="_blank"> <img :src="user.avatar_url" style='width: 100px'/> </a> <h4 class="card-title">{{user.login}}</h4> </div> <!-- 展示欢迎词 --> <h1 v-show="info.isFirst">欢迎使用!</h1> <!-- 展示加载中 --> <h1 v-show="info.isLoading">加载中...</h1> <!-- 展示错误信息 --> <h1 v-show="info.errMsg">{{errMsg}}</h1> </div> </template> <script> export default { name:'List', data() { return { info:{ isFirst:true, isLoading:false, errMsg:'', users:[] } } }, mounted(){ this.$bus.$on('updateListData',(dataObj)=>{ this.info = {...this.info,...dataObj} }) }, beforeDestroy(){ this.$bus.$off('updateListData') } } </script> <style scoped> .album { min-height: 50rem; /* Can be removed; just added for demo purposes */ padding-top: 3rem; padding-bottom: 3rem; background-color: #f7f7f7; } .card { float: left; width: 33.333%; padding: .75rem; margin-bottom: 2rem; border: 1px solid #efefef; text-align: center; } .card > img { margin-bottom: .75rem; border-radius: 100px; } .card-text { font-size: 85%; } </style>
总结:
vue项目常用的两个Ajax库:
axios:通用的Ajax请求库,官方推荐,效率高
vue-resource:vue插件库,vue 1.x使用广泛,官方已不维护
src/App.vue
:
<template> <div class="container"> <Category title="美食" > <img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt=""> </Category> <Category title="游戏" > <ul> <li v-for="(g,index) in games" :key="index">{{g}}</li> </ul> </Category> <Category title="电影"> <video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video> </Category> </div> </template> <script> import Category from './components/Category' export default { name:'App', components:{Category}, data() { return { games:['植物大战僵尸','红色警戒','空洞骑士','王国'] } }, } </script> <style scoped> .container{ display: flex; justify-content: space-around; } </style>
src/components/Category.vue
:
<template> <div class="category"> <h3>{{title}}分类</h3> <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) --> <slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot> </div> </template> <script> export default { name:'Category', props:['title'] } </script> <style scoped> .category{ background-color: skyblue; width: 200px; height: 300px; } h3{ text-align: center; background-color: orange; } video{ width: 100%; } img{ width: 100%; } </style>
效果:
src/App.vue
:
<template> <div class="container"> <Category title="美食" > <img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt=""> <a slot="footer" href="http://www.atguigu.com">更多美食</a> </Category> <Category title="游戏" > <ul slot="center"> <li v-for="(g,index) in games" :key="index">{{g}}</li> </ul> <div class="foot" slot="footer"> <a href="http://www.atguigu.com">单机游戏</a> <a href="http://www.atguigu.com">网络游戏</a> </div> </Category> <Category title="电影"> <video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video> <template v-slot:footer> <div class="foot"> <a href="http://www.atguigu.com">经典</a> <a href="http://www.atguigu.com">热门</a> <a href="http://www.atguigu.com">推荐</a> </div> <h4>欢迎前来观影</h4> </template> </Category> </div> </template> <script> import Category from './components/Category' export default { name:'App', components:{Category}, data() { return { games:['植物大战僵尸','红色警戒','空洞骑士','王国'] } }, } </script> <style> .container,.foot{ display: flex; justify-content: space-around; } h4{ text-align: center; } </style>
src/components/Category.vue
:
<template> <div class="category"> <h3>{{title}}分类</h3> <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) --> <slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot> <slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现2</slot> </div> </template> <script> export default { name:'Category', props:['title'] } </script> <style scoped> .category{ background-color: skyblue; width: 200px; height: 300px; } h3{ text-align: center; background-color: orange; } video{ width: 100%; } img{ width: 100%; } </style>
效果:
src/App.vue
:
<template> <div class="container"> <Category title="游戏" > <template scope="jojo"> <ul> <li v-for="(g,index) in jojo.games" :key="index">{{g}}</li> </ul> </template> </Category> <Category title="游戏" > <template scope="jojo"> <ol> <li v-for="(g,index) in jojo.games" :key="index">{{g}}</li> </ol> </template> </Category> <Category title="游戏" > <template scope="jojo"> <h4 v-for="(g,index) in jojo.games" :key="index">{{g}}</h4> </template> </Category> </div> </template> <script> import Category from './components/Category' export default { name:'App', components:{Category} } </script> <style> .container,.foot{ display: flex; justify-content: space-around; } h4{ text-align: center; } </style>
src/components/Category.vue
:
<template> <div class="category"> <h3>{{title}}分类</h3> <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) --> <slot :games="games">我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot> </div> </template> <script> export default { name:'Category', props:['title'], data() { return { games:['植物大战僵尸','红色警戒','空洞骑士','王国'] } }, } </script> <style scoped> .category{ background-color: skyblue; width: 200px; height: 300px; } h3{ text-align: center; background-color: orange; } video{ width: 100%; } img{ width: 100%; } </style>
效果:
总结:
插槽:
作用:让父组件可以向子组件指定位置插入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>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。