Vue
Hello World!
模板语法
插值语法 {{}}
用于解析标签体内容
<!--引入vue.js-->
<script src="js/vue.js" type="text/javascript"></script>
<div id="root">
<h1>
Hello {{name}}
</h1>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
//创建vue实例
new Vue({
// 在el配置项中 用css选择器选择对象
el:'#root',
//data中用于存储数据,供el所指定的对象所使用,
data:{
name:'pyy'
}
})
</script>
指令语法,用于解析标签(标签属性,绑定事件。。。)
vue有很多指令,形式都是 v-***
v-bind: 例:v-bind:href=“url” 多层级关系用.点召唤
可以简写成 : (冒号)
动态的绑定事件
<div id="root">
<br>
<a v-bind:href="url" target="_blank" >哩哔哔哩</a>
<a :href="url" target="_blank" >也是哩哔哔哩</a>
</div>
//创建vue实例
new Vue({
//用css选择器选择对象
el:'#root',
//data中用于存储数据,供el所指定的对象所使用,
data:{
url:'https://www.bilibili.com/'
}
})
数据绑定
1、单向数据绑定(v-bind:):数据只能从data流向页面
2、双向数据绑定(v-model:):数据不仅可以从data流向页面,还可以从页面流向data
注:v-model:只能应用在表单类元素上,输入的元素
<div id="pyy">
单向数据绑定:<input type="text" v-bind:value="name"><br>
双向数据绑定:<input type="text" v-model:value="name"><br>
<!-- 简写方式 -->
单向数据绑定:<input type="text" :value="name"><br>
双向数据绑定:<input type="text" v-model="name"><br>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el:'#pyy',
data:{
name:'pj'
}
})
</script>
el和data的第二种写法
<div id="pyy">
<h1>{{name}}</h1>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
const v = new Vue({
//data的第二种写法 函数式,在组件中,data必须为函数式写法,可以简写data(){} 不能写箭头,用了箭头就变成window的函数了
data:function() {
return {
name:'pj';
}
}
})
//与el效果一样,此为第二种写法
v.$mount('#pyy');
</script>
MVVM模型
<body>
<!-- 容器 -->
<!-- MVVM模型
M:model模型,data中的数据
V:view视图
VM: viewmodel视图模型 Vue实例
data中的所有属性,最终都会出现在vm(Vue实例)身上
vm(Vue实例)中的所有属性都可以在vue视图中直接用
-->
<div id="pyy">
<h1>{{name}}</h1>
<h1>{{age}}</h1>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
data(){
return{
name:'pj',
age:24
}
}
})
vm.$mount('#pyy');
</script>
数据代理
js中的 Object.defineProperty
<script>
let num = 20;
let person = {
name:'pyy',
sex:'men'
}
Object.defineProperty(person,'age',{
//value:18,//age属性的值
//enumerable:true,//是否可以被遍历,默认是false
//writable:true,//是否可以被修改,默认是false
//configurable:true,//是否可以被删除。默认是false
//当age属性被调用的时候,则执行getter方法,返回其中的返回值 这个值就是age
get(){
console.log("我看看几岁了 ");
return num
},
//当age属性被修改的时候,调用setter方法,将修改的值传递给num,从而修改age的值
set(value){
num = value
console.log('我今年是'+value+'岁');
}
})
</script>
什么是数据代理
通过一个对象代理 ,对另一个对象中属性的操作(读/写)
Vue中的数据代理
通过vm对象来代理data对象中属性的操作(读、写)
好处:更加方便的操作data中的数据
基本原理:通过Object.defineProperty()把data对象中所有属性添加到vm上
为每一个属性都指定一个getrer、setter。
事件处理
<!-- 事件使用
使用v-on:事件名或@事件名
事件的回调函数需要在配置项methods中,最后会在vm(vue实例对象)中
回调函数不能用箭头,否则this关键字就会变成window
this指向的是vm或者是组件实例对象
简写方式:@click="notShow($event,'gun')" 可以传递参数
完整写法:v-on:click="show" 也可以不传递参数
-->
<div id="pyy">
<button v-on:click="show" >点我</button>
<button @click="notShow($event,'gun')" >别点我</button>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
methods:{
show(){
alert("点我干嘛");
},
notShow(e,e2){
console.log(e,e2);
alert("说了别点还点我");
}
}
})
vm.$mount('#pyy');
</script>
事件修饰符
<style>
.wai {
margin-top: 50px;
width: 100px;
height: 100px;
background-color: pink;
}
.nei{
width: 55px;
height: 55px;
background-color: greenyellow;
}
</style>
<!--
vue中的修饰符
prevent:阻止默认事件
stop:阻止事件冒泡(禁止套娃,触发里层的效果,不让外层触发)
once:事件只触发一次
capture:使用事件的捕获模式(先捕获再冒泡,在外层加了这个后,会在捕获阶段就执行)
self:只有event.target是当前操作的元素时才会触发事件
passive:事件的默认行为立即执行,无需等待事件回调执行完毕(用的较少)
修饰符可以连续写,可以达到多重效果 例 @click.stop.prevent="nei"
-->
<div id="pyy">
<!-- 加了 prevent 就不能跳转了 -->
<a href="https://www.baidu.com" @click.prevent="baidu">百度一下,你就知道。</a>
<!-- 加了 stop 就只出现里面的一个弹框,外部的弹框不会弹了,阻止了冒泡 -->
<div class="wai" @click="nei">
<button @click.stop="nei">点我</button>
</div>
<!-- 加了 once 就只能点击一次了 -->
<button @click.once="one">点我一下</button>
<!-- 加了 capture 会先执行外部,后执行内部 -->
<div class="wai" @click.capture="wai">
d1
<div class="nei" @click="nei">
d2
</div>
</div>
<!-- 加了 self 如果点击了并且当前点击的target是自己,那么则会触发,如果是冒泡冒上来的则不会触发-->
<div class="wai" @click.self="ziji">
<button @click="ziji">点我自己</button>
</div>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
methods: {
baidu() {
alert("不准跳");
},
nei() {
console.log("d2");
},
wai() {
console.log("d1");
},
one(){
alert("一下");
},
ziji(e){
console.log(e.target);
}
}
})
vm.$mount('#pyy')
</script>
键盘事件
<!--
键盘监听事件
@keyup:键盘抬起时触发
@keydown:键盘按下时触发
常用按键别名:
回车===enter
删除===delete
退出===esc
空格===space
换行===tab(特殊,要配合keydown使用)
上===up
下===down
左===left
右===right
vue未提供别名的按键,可以使用按键原始的key值去绑定 例:Caps-lock
ctrl、alt、shift、win键在配合keyup使用的时候需要按下抬起其他任意的键才能触发,keydown正常触发
可以自定义别名
Vue.config.keyCodes.自定义按键名 = 键码
指定组合按键:例 @keyup.crrl.y 按下ctrl+y才能触发,别的都不能触发
-->
<div id="pyy">
<!-- 按下回车触发事件 -->
<input type="text" placeholder="输入内容" @keyup.enter="show">
</div>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
methods:{
show(e){
console.log(e.target.value);
}
}
})
vm.$mount('#pyy')
</script>
计算属性
用过已有的属性计算得来的属性,
借助了 Object.defineProperty 的getter和setter
内部有缓存机制,效率高,调试方便
计算属性最终会出现在vm上,直接读取即可
修改计算属性时,需通过set函数去修改,并且把修改的值,赋给所依赖的属性
<div id="pyy">
姓:<input type="text" v-model="firstName">
名:<input type="text" v-model="lastName">
全名:<span>{{fullName}}</span>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
data: {
firstName: '庞',
lastName: '杰'
},
computed: {
fullName: {
get() {
//get方法在第一次读取fullName时被调用,和所依赖的数据发生变化是被调用
/*
vm.fullName = '诸葛-孔明'
get被调用
'诸葛-孔明'
*/
console.log('get被调用');
return this.firstName + '-' + this.lastName
},
set(value) {
//将传进来的值,按照-分开姓和名,然后赋值给firstName和lastName
const temp = value.split('-');
this.firstName = temp[0];
this.lastName = temp[1];
}
}
}
})
vm.$mount('#pyy')
</script>
一般情况不做修改,只有展示效果。这种情况可以使用简写模式
```javascript
computed: {
fullName(){
return this.firstName + ‘-’ + this.lastName
}
}
```
监视属性
当被监视的属性变化时,回调函数自动调用,进行相关操作
监视属性有两种写法
深度监视
vue中的watch默认不监视对象内部中数据的改变
配置deep:true后,就可以监视对象内部数据的的改变
如果不需要深度监视,则不开启,影响效率
<div id="pyy">
<h1>今天很{{info}}</h1>
<button @click="change">切换</button>
<hr>
<h3>x的值是{{nums.x}}</h3><br>
<button @click="nums.x++">x++</button><br>
<h3>y的值是{{nums.y}}</h3><br>
<button @click="nums.y++">y++</button><br>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
data: {
isHot: true,
nums:{
x:0,
y:0
}
},
computed: {
info() {
return this.isHot ? 'hot' : 'dream'
}
},
methods: {
change() {
this.isHot = !this.isHot;
}
},
//监视属性,第一种写法
watch: {
isHot: {
immediate: true,//初始化时让handler调用一下
//参数:修改后新的值,修改前旧的值
handler(newValue, oldValue) {
console.log(newValue, oldValue);
}
},
//监视多急结构中某个属性的变化
'nums.x':{
handler(){
console.log('x++');
}
},
//监视多急结构中所有属性的变化
nums:{
deep:true,
handler(){
console.log('nums');
}
}
}
})
vm.$mount('#pyy');
//监视属性,第二种写法
// vm.$watch('isHot', {
// immediate: true,//初始化时让handler调用一下
// //参数:修改后新的值,修改前旧的值
// handler(newValue, oldValue) {
// console.log(newValue, oldValue);
// }
// });
</script>
绑定样式
.basic{
width: 400px;
height: 400px;
border: 1px solid #000000;
}
.red{
background-color: red;
}
.green{
background-color: green;
}
.blue{
background-color: blue;
}
.pj{
border-radius: 30%;
}
<div id="pyy">
<!-- 绑定css样式,字符串写法,,适用于样式的名字不确定,需要动态的指定 -->
<!-- 例:点击div随机切换颜色 -->
<div class="basic" v-bind:class="color" @click="change">
{{name}}
</div>
<hr>
<!-- 绑定css样式,数组写法,,适用于样式的名字不确定,个数也不确定 -->
<div class="basic" v-bind:class="colorArr" >
{{name}}
</div>
<!-- 绑定css样式,对象写法,,适用于样式的名字确定,个数也确定 但是谁用谁不用还没确定 -->
<div class="basic" v-bind:class="colorObj" >
{{name}}
</div>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
data:{
name:'pyy',
color:'red',
colorArr:['blue','pj'],
colorObj:{
red:true,
pj:true
}
},
methods: {
change(){
const arr = ['red','green','blue']
const index = Math.floor(Math.random()*3);
this.color = arr[index];
}
},
})
vm.$mount('#pyy')
</script>
条件渲染
v-show:‘true/false’ 控制显示与不显示,但是语句还在,只是加了个属性 style=“display: none;” 切换频率较高时使用
v-if:‘true/false’ 直接把整个语句都干掉 (元素可能拿不到了) 切换频率较低时使用 与 v-else-if v-else 一起使用,注:结构不能被打断。
<div id="pyy">
<h2>{{name}}:{{n}}</h2>
<button @click="n++">n++</button>
<!-- <h3 v-show="n===1">李逍遥</h3>
<h3 v-show="n===2">赵灵儿</h3> -->
<!-- <h3 v-if="n===1">李逍遥</h3>
<h3 v-if="n===2">赵灵儿</h3> -->
<h3 v-if="n===0">庞杰</h3>
<h3 v-else-if="n===1">李逍遥</h3>
<h3 v-else-if="n===2">赵灵儿</h3>
<h3 v-else>邪剑仙</h3>
<!-- v-if与 template配合使用-->
<template v-if="n===6">
<h3>我是谁</h3>
<h3>我是谁</h3>
<h3>我是谁</h3>
<h3>我是谁</h3>
</template>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
data: {
name: 'pyy',
n: 0
}
})
vm.$mount('#pyy')
</script>
列表渲染
v-for 用于展示列表数据
<div id="pyy">
<!-- 遍历数组 用的较多 -->
<!-- p就是数组中的每一个元素,index就是索引 :key="index" 每一个的标识-->
<ul>
<li v-for="(p,index) in persons" :key="index">
{{index}}:{{p.name}}-{{p.age}}
</li>
</ul>
<!-- 遍历对象 用的较多 -->
<!-- value就是对象中的值,index就是索引 :key="index" 每一个的标识-->
<ul>
<li v-for="(value,index) in human" :key="index">
{{index}}:{{value}}
</li>
</ul>
<!-- 遍历字符串 用的较少 -->
<ul>
<li v-for="(value,index) in str" :key="index">
{{index}}:{{value}}
</li>
</ul>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
data:{
persons:[
{id:1001,name:'pyy',age:40},
{id:1002,name:'pxx',age:30},
{id:1003,name:'pzz',age:20}
],
human:{
name:'pj',
height:185,
weight:75
},
str:'pangjie'
},
})
vm.$mount('#pyy')
</script>
列表过滤
<div id="pyy">
<input type="text" placeholder="请输入姓名" v-model="keyWords">
<ul>
<li v-for="(p,index) in filPersons" :key="p.id">
{{p.name}}-{{p.age}}
</li>
</ul>
</div>
<script>
Vue.config.productionTip = false
//使用监视来进行列表过滤
// const vm = new Vue({
// data:{
// keyWords:'',
// persons:[
// {id:1001,name:'马冬梅',age:40},
// {id:1002,name:'赵四',age:30},
// {id:1003,name:'赵灵儿',age:20},
// {id:1004,name:'郭冬临',age:30}
// ],
// filPersons:[]
// },
// watch:{
// keyWords:{
// immediate:true,
// handler(newValue){
// //过滤方法
// this.filPersons = this.persons.filter((p)=>{
// // 如果输入框的内容在姓名中找不到,会返回-1,如果有,则会返回在第几位(从零开始)
// return p.name.indexOf(newValue) != -1;
// })
// }
// }
// }
// })
// vm.$mount('#pyy')
//使用计算属性来进行列表过滤,计算属性和监视属性都能完成的情况下,尽量用计算属性
const vm = new Vue({
data:{
keyWords:'',
persons:[
{id:1001,name:'马冬梅',age:40},
{id:1002,name:'赵四',age:30},
{id:1003,name:'赵灵儿',age:20},
{id:1004,name:'郭冬临',age:30}
],
},
computed:{
filPersons:{
get(){
//过滤方法
return this.persons.filter((p)=>{
// 如果输入框的内容在姓名中找不到,会返回-1,如果有,则会返回在第几位(从零开始)
return p.name.indexOf(this.keyWords) != -1;
})
}
}
}
})
vm.$mount('#pyy')
</script>
列表排序
<div id="pyy">
<input type="text" placeholder="请输入姓名" v-model="keyWords">
<button @click="sort=1">年龄升序</button>
<button @click="sort=2">年龄降序</button>
<button @click="sort=0">原升序</button>
<ul>
<li v-for="(p,index) in filPersons" :key="p.id">
{{p.name}}-{{p.age}}
</li>
</ul>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
data:{
sort:0,
keyWords:'',
persons:[
{id:1001,name:'马冬梅',age:44},
{id:1002,name:'赵四',age:33},
{id:1003,name:'赵灵儿',age:23},
{id:1004,name:'郭冬临',age:37}
],
},
computed:{
filPersons:{
get(){
//过滤方法
const arr = this.persons.filter((p)=>{
// 如果输入框的内容在姓名中找不到,会返回-1,如果有,则会返回在第几位(从零开始)
return p.name.indexOf(this.keyWords) != -1;
})
//判断是否需要排序,0为false,1、2为真,需要排序
if(this.sort){
arr.sort((a,b)=>{
return this.sort ===1 ? a.age - b.age : b.age - a.age;
})
}
return arr;
}
}
}
})
vm.$mount('#pyy')
</script>
vue数据监测
总结
原理:vue会监视data中所有层次的数据
通过setter实现监视,并且在new Vue 的时候就传入监测的数据
对象中后追加的数据vue不做响应式处理(get、set),要想后加的数据有响应式处理,
请使用vue的api 例:
this.$set(this.student,‘sex’,‘男’) this是vue实例
Vue.set(this.student,‘sex’,‘男’) Vue 是vue的全局变量
数组中的数据是通过包裹数组的方法实现的,也就是,调用原生的数组方法,然后在解析模板,进而对页面进行更新。
修改数组的元素时,要用方法修改。例:push(),pop(),shift(),unshift(),splice(),reverse()
或者 Vue.set() vm.$set() 注:这两个方法不能给vue实例的根数据添加属性
<div id="app">
<h2>学生信息</h2>
<hr>
<button @click="student.age++">年龄+1岁</button><br><br>
<button @click="addSex">添加性别属性,默认值:男</button><br><br>
<button @click="addFriend" >在列表首位添加一个朋友</button><br><br>
<button @click="updateFirstFriendName">修改第一个朋友名字为张三</button><br><br>
<button @click="addHobby">添加一个爱好</button><br><br>
<button @click="updateFirstHobby">修改第一个爱好为开车</button><br><br>
<hr>
<h4>姓名:{{student.name}}</h4>
<h4>年龄:{{student.age}}</h4>
<h4 v-if="student.sex">性别:{{student.sex}}</h4>
<h4>爱好</h4>
<ul>
<li v-for="(h,index) in student.hobby " ::key="index">
{{h}}
</li>
</ul>
<h4>朋友</h4>
<ul>
<li v-for="(f,index) in friends " ::key="index">
{{f.name}}----{{f.age}}
</li>
</ul>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
data:{
student:{
name:'peter',
age:18,
hobby:['抽烟','喝酒','烫头']
},
friends:[
{name:'tom',age:19},
{name:'kity',age:22},
{name:'lisa',age:17}
]
},
methods: {
addSex(){
this.$set(this.student,'sex','男')
//Vue.set(this.student,'sex','男')
},
addFriend(){
this.friends.unshift({name:'jack',age:33});
},
updateFirstFriendName(){
this.friends[0].name = '张三';
},
addHobby(){
this.student.hobby.push('看电影')
},
updateFirstHobby(){
this.student.hobby.splice(0,1,'开车');
}
},
})
vm.$mount('#app')
</script>
收集表单数据
v-model的三个修饰符:
lazy失去焦点在收集数据
number:输入的字符串转为有效的数字
trim:输入收尾空格过滤掉
<div id="app">
<form @submit.prevent = 'demo'>
<!-- trim去掉前后空格 -->
<label >账号:<input type="text" v-model.trim="user.accont" ></label><br><br>
<label >密码:<input type="password" v-model="user.password"></label><br><br>
<!-- number 使其输入为number类型 -->
<label >年龄:<input type="number" v-model.number="user.age"></label><br><br>
<!-- type属性为radio单选按钮时,需要手动写value -->
<label >性别:</label>
<input type="radio" v-model="user.sex" value="man">男
<input type="radio" v-model="user.sex" value="women">女<br><br>
<!-- type属性为checkbox多选框时,需要手动写value 而且用数组存放,否则返回的就是布尔值-->
<label >爱好:</label>
<input type="checkbox" v-model="user.hobby" value="study">学习
<input type="checkbox" v-model="user.hobby" value="play">玩游戏
<input type="checkbox" v-model="user.hobby" value="watch">看电视
<br><br>
地址:
<select v-model="user.address">
<option value="beijing">北京</option>
<option value="tianjin">天津</option>
<option value="hebei">河北</option>
<option value="dongbei">东北</option>
</select>
<br><br>
其他信息
<!-- lazy 失去焦点的时候再把内容放入vm中 -->
<textarea cols="30" rows="10" v-model.lazy="user.otherInfo">
</textarea>
<br><br>
<p><input type="checkbox" v-model="user.agree">阅读并接受<a href="www.baidu.com">《用户协议》</a></p>
<br><br>
<button >提交</button>
</form>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
data:{
user:{
name:'',
password:'',
age:'',
sex:'man',
hobby:[],
address:'tianjin',
otherInfo:'',
agree:''
}
},
methods:{
demo(){
if(!this.user.agree){
alert("请先阅读并同意用户协议");
return;
}
console.log(JSON.stringify(this.user));
}
}
})
vm.$mount('#app')
</script>
过滤器(vue3已经移除了)
搭配插值语法{{}} 或者v-bind使用
<div id="app">
<h2>时间格式化 {{time}}</h2>
<h3>计算属性:{{formatTime}}</h3>
<h3>方法属性:{{fmethTime()}}</h3>
<!-- 将time作为 firstFilter的第一个参数传递-->
<h3>过滤器1:{{time | firstFilter}}</h3>
<!-- 将time作为 secondFilter的第一个参数传递 括号内容作为第二个参数传递-->
<h3>过滤器2:{{time | secondFilter('YYYY-MM-DD')}}</h3>
<!-- 将time作为 firstFilter的第一个参数传递 将firstFilter的返回值作为 mySlice 的第一个参数传递-->
<h3>全局过滤器:{{time | firstFilter | mySlice}}</h3>
</div>
<script>
Vue.config.productionTip = false
//定义一个全局过滤器
Vue.filter('mySlice',function(value){
//截取前四位
return value.slice(0,4);
})
const vm = new Vue({
data: {
time: '1677421202346'
},
computed: {
formatTime() {
return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss');
}
},
methods:{
fmethTime(){
return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss');
}
},
//在vue实例里面定义的过滤器只能在这个vue实例中用别的vue实例用不了
filters:{
firstFilter(value){
return dayjs(value).format('YYYY-MM-DD HH:mm:ss');
},
secondFilter(value,str){
return dayjs(value).format(str);
}
}
})
vm.$mount('#app')
</script>
内置指令
v-text :向其所在的节点中渲染文本内容
<h2 v-text="time"> </h2>
data: {
time: '1677421202346'
},
v-html:可以解析标签 可以识别html结构
如果在网站上动态渲染任意html是非常危险的,容易导致xss攻击。不要用在用户提交上。
<h2 v-html="time"> </h2>
data: {
time: '</h4>1677421202346</h4>'
},
v-cloak:本质是一个特殊属性,vue实例创建完毕并接管容器后会删掉v-cloak属性
搭配css可以解决网速慢是页面显示{{xxx}}的情况
v-once:所在节点在初次动态渲染后,就视为静态内容了,以后数据发生变化就不会影响它了。
v-pre: 跳过所在节点的编译,在容器中如果有不需要编译的标签,可以加一个v-pre,这样就加快了编译速度。
自定义指令
<div id="app">
<h2>n的值是: <span v-text="n"></span></h2>
<h2>n的值乘十是: <span v-ten="n"></span></h2>
<h2>n的值乘二十是: <span v-big="n"></span></h2>
<button @click="n++">n++</button>
<input type="text" name="" id="" v-fbind:value="n">
</div>
<script>
Vue.config.productionTip = false
//全局指令,和过滤器写法一样
Vue.directive('big',function(el, binding){
el.innerText = binding.value * 20;
})
const vm = new Vue({
data: {
n: '1'
},
//自定义指令的配置项,指令名不要用驼峰形式命名,可以用-连接
directives: {
//参数一:当前指令所在的标签
//参数二:包含指令的信息,名字,使用时的名字,value值===指令所用到属性值
//简写,函数式,在指令与元素绑定成功时会被调用,就是指令所在的模板被重新解析时会被调用
ten(el, binding) {
//将属性值,赋值给指令所在标签文本
el.innerText = binding.value * 10;
},
//较为完整的写法
fbind: {
//指令与元素绑定成功
bind(el, binding) {
el.value = binding.value;
},
//指令所属标签插入页面时,焦点
inserted(el, binding) {
el.focus();
},
//指令所在的模板被重新解析
update(el, binding) {
el.value = binding.value;
}
}
}
})
vm.$mount('#app')
</script>
生命周期
beforeCreate(){} --此时无法通过vm访问到data中的数据和methods中的方法
created(){}----此时可以通过vm访问到data中的数据和methods中的方法
这个阶段vue开始解析模板,生成虚拟dom,但是页面还不能显示解析好的内容
beforeMount(){}—页面上显示的是未经过vue编译的dom
将虚拟dom转为真实dom
mounted(){}—vue完成模板解析并把初始的真实dom放入页面中后,调用mounted(){}。
beforeUpdate(){} —数据是新的,但是页面还是旧的。
updated(){}—数据和页面都是新的
beforeDsetory(){}-----一般在此阶段关闭定时器,解绑自定义事件,等一些收尾操作
destoryed—销毁时自定义事件会失效,但是原生dom事件依然有效,
组件
局部功能的代码集合。复用编码,提高效率。
非单文件组件
<div id="app">
<h2>{{hello}}</h2>
<!-- 使用组件(第三步) -->
<ren></ren>
<hr>
<pj></pj>
<pyy></pyy>
</div>
<script>
Vue.config.productionTip = false
//创建组件(第一步)
const person = Vue.extend({
data(){
return {
name:'pyy',
address:'erath'
}
},
template:`
<div>
<h2>我是谁?{{name}}</h2>
<h2>我在哪?{{address}}</h2>
</div>
`
});
const pj = Vue.extend({
//data要写成函数,
data(){
return {
name:'pj',
level:'100',
}
},
//配置组件架构
template:`
<div>
<h2>我是谁?{{name}}</h2>
<h2>我多少级?{{level}}</h2>
<button @click="level++">等级加一</button>
</div>
`
})
const pyy = Vue.extend({
data(){
return {
name:'pyy',
}
},
template:`
<div>
<h2>ta是谁?{{name}}</h2>
</div>
`
})
//注组件(第二步)(全局组件)
Vue.component('pyy',pyy)
const vm = new Vue({
data:{
hello:'你好,庞杰'
},
//注组件(第二步)(局部组件)
components:{
//标签名:组件名
ren:person,
//如果二者相同可以简写
pj
}
})
vm.$mount('#app')
</script>
组件嵌套
创建一个,一人之下万人之上的组件,来管理其他组件
<div id="pyy">
</div>
<script>
Vue.config.productionTip = false
const pang = Vue.extend({
name:'pang',
//data要写成函数,
data(){
return {
name:'pj',
level:'100',
}
},
//配置组件架构
template:`
<div>
<h2>我是谁?{{name}}</h2>
<h2>我多少级?{{level}}</h2>
<button @click="level++">等级加一</button>
</div>
`
})
//创建组件(第一步)
const person = Vue.extend({
name:'person',
data(){
return {
name:'pyy',
address:'erath'
}
},
template:`
<div>
<h2>我是谁?{{name}}</h2>
<h2>我在哪?{{address}}</h2>
<pang></pang>
</div>
`,
components:{
pang
}
});
const app = Vue.extend({
name:'app',
components:{
//标签名:组件名
ren:person,
//如果二者相同可以简写
},
//需要用div包裹起来,否则只能识别出一个标签
template:`
<div>
<ren></ren>
</div>
`,
})
const vm = new Vue({
template:`
<app></app>
`,
//注组件(第二步)(局部组件)
components:{
app
}
})
vm.$mount('#pyy')
</script>
VueComponent构造函数
组件本质是一个名为VueComponent的构造函数 在Vue.extend时生成 一个全新的VueComponent
vue解析组件标签时,就会创建一个组件的实例对象,即: new VueComponent()
这时,这里的 this就是VueComponent的实例对象
组件实例对象也可以访问到vue原型上的属性、方法。
单文件组件
一个组件一个.vue文件,配合脚手架使用。
脚手架
ref属性:
ref=“xxx”
this.$refs.xxx
被用来给元素或子组件注册引用信息(代替id)
应用在html标签上获取的是真实DOM元素,用在组件上获取到的是组件实例
App.Vue
<template>
<div>
<h2 v-text="message" ref="title"></h2>
<button @click="log" ref="btn">输出信息</button>
<School ref="ss"></School>
</div>
</template>
<script>
import School from './components/School.vue'
export default {
name:'App',
components:{
School
},
data() {
return {
message:'hello Vue'
}
},
methods: {
log(){
console.log(this.$refs.title);//真实Dom元素
console.log(this.$refs.btn);//真实Dom元素
console.log(this.$refs.ss);//School组件的实例
}
},
}
</script>
<style>
</style>
School.Vue
<template>
<div class="school">
<h2 >学校地址:{{address}}</h2>
</div>
</template>
<script>
export default {
name:'School',
data(){
return {
address:'earth'
}
}
}
</script>
<style>
.school{
background-color: pink;
}
</style>
main.js(基本不会改动里面的内容)
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
props配置
让组件接收外部传进来的数据
App.vue
<template>
<div>
<!-- 传递数据 -->
<!-- 将age用v-bind数据绑定,name引号中的内容就不是字符串了,就会被解析成js表达式 -->
<Student name="庞杰" :age="24" sex="男"></Student>
<Student name="赵丽颖" sex="女"></Student>
</div>
</template>
<script>
import Student from './components/Student.vue'
export default {
name:'App',
components:{
Student
},
}
</script>
<style>
</style>
Student.vue
<template>
<div class="school">
<h2 >{{msg}}</h2>
<h2 >姓名:{{name}}</h2>
<h2 >年龄:{{myAge}}</h2>
<h2 >性别:{{sex}}</h2>
<button @click="myAge++" >长了一岁</button>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return {
msg:'我是一个人!!',
//age是外部传递进来的变量,尽量不要修改。如果要修改age的值
//定义一个中间变量,用来存放age
myAge:this.age
}
},
//第一种接收数据方式,简单接收
//props:['name','age','sex']
//第二种接收数据方式,限制接收类型
// props:{
// name:String,
// age:Number,
// sex:String
// }
//第三种接收数据方式(最完整),限制接收类型+是否必传+是否有默认值
props:{
name:{
typeof:String,
required:true,//必须传递
},
age:{
typeof:Number,
default:18//人如果不传递age属性,那么默认为18岁
},
sex:{
typeof:String,
required:true
}
}
}
</script>
<style>
.school{
background-color: pink;
}
</style>
mixin(混入/混合)
在不同组件中使用相同的东西,则可以把这个东西单独提出来,—混合对象。
谁用谁导入
App.vue
<template>
<div>
<!-- 传递数据 -->
<!-- 将age用v-bind数据绑定,name引号中的内容就不是字符串了,就会被解析成js表达式 -->
<Student ></Student>
<School ></School>
</div>
</template>
<script>
import Student from './components/Student.vue'
import School from './components/School.vue'
export default {
name:'App',
components:{
Student,School
},
}
</script>
<style>
</style>
School.vue
<template>
<div class="school">
<h2 @click="show">姓名:{{name}}</h2>
<h2 >地址{{address}}</h2>
</div>
</template>
<script>
//import {mixin} from '../mixin';
export default {
name:'School',
data(){
return {
name:'zhonghuan',
address:'erath'
}
},
//混合配置,数组的形式,用几个放几个
//mixins:[mixin]
}
</script>
<style>
.school{
background-color: pink;
}
</style>
Student.vue
<template>
<div class="school">
<h2 @click="show">姓名:{{name}}</h2>
<h2 >年龄:{{age}}</h2>
<h2 >性别:{{sex}}</h2>
</div>
</template>
<script>
//导入混合文件(局部),属性、方法会混合到一起,如果属性名或方法名一样,则按照自己的来,不会按照混合文件的内容
//import {mixin} from '../mixin';
export default {
name:'Student',
data(){
return {
name:'pyy',
age:'24',
sex:'men',
a:'aaa'
}
},
//混合配置,数组的形式,用几个放几个
//mixins:[mixin]
}
</script>
<style>
.school{
background-color: pink;
}
</style>
mixin.js
export const mixin = {
methods:{
show(){
alert(this.name);
console.log('hello');
}
},
data(){
return{
a:'a'
}
}
}
main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//导入混合文件(全局)会在所有组件和vm身上加入混合中的东西(包括app。vue实例)三思而后行
import {mixin} from './mixin'
Vue.mixin(mixin);
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
插件
用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是vue,第二个以后的参数是插件使用者传递的数据
定义插件:对象.install = function(Vue,options){
全局过滤器。。。
全局指令。。。
全局混入。。。
实例方法。。。
}
使用插件:Vue.use()
scoped样式
局部的,在.vue文件里的style标签加一个这个属性
那么这个style标签里的所有装饰只能服务于本文件里的架构了。
TodoList案例
尝试组件化编码
App.vue
<template>
<div >
<div class="todo-container">
<div class="todo-wrap">
<MyHeader :addTodo="addTodo"></MyHeader>
<MyList :todos="todos" :changeTodo="changeTodo" :deleteTodo="deleteTodo"></MyList>
<MyFooter :todos="todos" :checkedAll="checkedAll" :clearDoneTodo="clearDoneTodo"></MyFooter>
</div>
</div>
</div>
</template>
<script>
import MyHeader from './components/MyHeader.vue'
import MyFooter from './components/MyFooter.vue'
import MyList from './components/MyList.vue'
export default {
name:'App',
data() {
return {
todos:[
{id:'1001',title:'吃饭',done:true},
{id:'1002',title:'睡觉',done:true},
{id:'1003',title:'打豆豆',done:false}
],
}
},
components:{
MyHeader,MyFooter,MyList
},
methods:{
//添加方法:参数就是输入框所输入内容所在的对象
addTodo(todoObj){
//添加到集合的头部
this.todos.unshift(todoObj);
},
//修改方法,根据id去修改todo对象的done
changeTodo(id){
//遍历todos集合,找到与参数id一直的对象
this.todos.forEach((todo)=>{
//将该对象的done值取反
if(todo.id === id) todo.done = !todo.done
})
},
//删除方法,根据id删掉改对象
deleteTodo(id){
//过滤一下,将符合条件的留下,形成一个新的集合,并赋值给todos
this.todos = this.todos.filter((todo)=>{
return todo.id !== id;
})
},
//全选/全不选方法,参数就是,底部多选框的值
checkedAll(value){
//将参数赋值给数组中的每一个对象的done值
this.todos.forEach((todo)=>{
todo.done = value;
})
},
//过滤掉已经完成的
clearDoneTodo(){
this.todos = this.todos.filter((todo)=>{
return !todo.done
})
}
}
}
</script>
<style>
/*base*/
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>
MyHeader.vue
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model.trim="title" @keyup.enter="show"/>
</div>
</template>
<script>
import {nanoid} from 'nanoid'
export default {
name:'MyHeader',
data() {
return {
title:''
}
},
props:['addTodo'],
methods:{
show(){
//检查输入内容
if(this.title.trim() ==='' || this.title.trim() ==='null' || this.title.trim() ==='undefined') return alert("输入内容不能为空!null undefined ")
//将输入内容装进对象
const item = {id:nanoid(),title:this.title,done:false};
//调用添加方法
this.addTodo(item);
//清空输入框中内容
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>
MyList.vue
<template>
<ul class="todo-main">
<MyItem
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj"
:changeTodo="changeTodo"
:deleteTodo="deleteTodo">
</MyItem>
</ul>
</template>
<script>
import MyItem from './MyItem.vue'
export default {
name:'MyList',
components:{
MyItem
},
props:['todos','changeTodo','deleteTodo']
}
</script>
<style scoped>
/*main*/
.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>
MyItem.vue
<template>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="changeDone(todo.id)"/>
<span>{{todo.title}}</span>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)" >删除</button>
</li>
</template>
<script>
export default {
name:'MyItem',
data() {
return {
}
},
props:['todo','changeTodo','deleteTodo'],
methods:{
//将修改的多选框对象的id作为参数
changeDone(id){
//调用爷爷传给爸爸的修改方法
this.changeTodo(id);
},
//将删除的对象的id作为参数
handleDelete(id){
if(confirm('确定删除吗')){
this.deleteTodo(id);
}
}
}
}
</script>
<style scoped>
/*item*/
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;
display: inline-block;
}
li:last-child {
border-bottom: none;
}
li:hover {
background-color: #dddddd;
}
li:hover button{
display: block;
}
</style>
MyFooter.vue
<template>
<div class="todo-footer" v-show="total">
<label>
<input type="checkbox" v-model="isAll"/>
</label>
<span>
<span>已完成{{completed}}</span> / 全部{{total}}
</span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
</div>
</template>
<script>
export default {
name:'MyFooter',
props:['todos','checkedAll','clearDoneTodo'],
computed:{
total(){
return this.todos.length
},
completed(){
//循环遍历(第一个参数上一次循环返回的值,第二个参数,当前循环的对象)
return this.todos.reduce((pre,todo)=>{
//如果done为真,就加一
return pre + (todo.done ? 1 : 0);
},0)
},
//计算属性的set/get方法
isAll:{
get(){
//返回值布尔类型,判断是否全选
return this.total === this.completed && this.total > 0;
},
set(value){
//调用更改全部多选框的方法,将底部多选框的值传递进去
this.checkedAll(value)
}
}
},
methods:{
//调用App的清除方法
clearAll(){
this.clearDoneTodo();
}
}
}
</script>
<style scoped>
/*footer*/
.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>
webStorage
存储内容大小一般支持5mb
浏览器通过Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制
setItem(‘key’,‘value’)
getItem(‘key’) 返回vlaue值
removeItem(‘key’) 删除
clear() 清空
完善todoList案例
添加了watch监视属性和默认数据读取
App.vue
<template>
<div >
<div class="todo-container">
<div class="todo-wrap">
<MyHeader :addTodo="addTodo"></MyHeader>
<MyList :todos="todos" :changeTodo="changeTodo" :deleteTodo="deleteTodo"></MyList>
<MyFooter :todos="todos" :checkedAll="checkedAll" :clearDoneTodo="clearDoneTodo"></MyFooter>
</div>
</div>
</div>
</template>
<script>
import MyHeader from './components/MyHeader.vue'
import MyFooter from './components/MyFooter.vue'
import MyList from './components/MyList.vue'
import { watch } from 'vue'
export default {
name:'App',
data() {
return {
//从本地读取数据
todos: JSON.parse(localStorage.getItem('todos')) || [],
}
},
components:{
MyHeader,MyFooter,MyList
},
methods:{
//添加方法:参数就是输入框所输入内容所在的对象
addTodo(todoObj){
//添加到集合的头部
this.todos.unshift(todoObj);
},
//修改方法,根据id去修改todo对象的done
changeTodo(id){
//遍历todos集合,找到与参数id一直的对象
this.todos.forEach((todo)=>{
//将该对象的done值取反
if(todo.id === id) todo.done = !todo.done
})
},
//删除方法,根据id删掉改对象
deleteTodo(id){
//过滤一下,将符合条件的留下,形成一个新的集合,并赋值给todos
this.todos = this.todos.filter((todo)=>{
return todo.id !== id;
})
},
//全选/全不选方法,参数就是,底部多选框的值
checkedAll(value){
//将参数赋值给数组中的每一个对象的done值
this.todos.forEach((todo)=>{
todo.done = value;
})
},
//过滤掉已经完成的
clearDoneTodo(){
this.todos = this.todos.filter((todo)=>{
return !todo.done
})
}
},
watch:{
todos:{
deep:true,
handler(value){
//本地存储,(存储到硬盘)
localStorage.setItem('todos',JSON.stringify(value))
}
}
}
}
</script>
<style>
/*base*/
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>
自定义事件
组件之间的通信方式,适用于 子组件—》父组件
App.vue
<template>
<div>
<!-- 子传父 -->
<School :getSchoolName="getSchoolName"></School>
<!-- 自定义事件 pyy 就是Studnt实例对象的一个自定义事件,,调用的方法就是getStudnetName-->
<!-- @click.native="show" native修饰符,表示使用原生click事件,如果不加这个修饰符则会被判定为自定义事件 -->
<Student v-on:pyy ="getStudnetName" @click.native="show"></Student>
<!-- 使用ref 直接获取到student实例对象,直接在这个对象身上添加自定义事件 此方法比较灵活-->
<!-- <Student ref="student"></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("app收到学校名称",name);
},
getStudnetName(name){
console.log("app收到学生姓名",name);
}
},
mounted(){
//获取到student实例对象,并且添加一个自定义事件调用getStudnetName方法
//this.$refs.student.$on('pyy',this.getStudnetName)
}
}
</script>
<style>
</style>
School.vue
<template>
<div class="school">
<h2 >姓名:{{name}}</h2>
<h2 >地址{{address}}</h2>
<button @click="sendSchoolName">发送名字</button>
</div>
</template>
<script>
export default {
name:'School',
data(){
return {
name:'zhonghuan',
address:'erath'
}
},
props:['getSchoolName'],
methods:{
sendSchoolName(){
this.getSchoolName(this.name);
}
}
}
</script>
<style>
.school{
background-color: pink;
}
</style>
Student.vue
<template>
<div class="school">
<h2 >姓名:{{name}}</h2>
<h2 >年龄:{{age}}</h2>
<h2 >性别:{{sex}}</h2>
<button @click="sendStudnetName">用自定义事件发送名字</button>
<button @click="unbind ">解绑自定义事件</button>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return {
name:'pyy',
age:'24',
sex:'men',
a:'aaa'
}
},
methods:{
sendStudnetName(){
//触发自定义事件pyy 并且传递一个参数
this.$emit('pyy',this.name)
},
unbind(){
//解绑自定义事件,一个事件直接写'事件名' 多个事件 写成数组 ['事件名','事件名'],不写参数则解绑全部自定义事件
this.$off('pyy');
console.log('pyy解绑成功')
}
}
}
</script>
<style>
.school{
background-color: pink;
}
</style>
全局事件总线
任意组件之间通信
安装全局事件总线
new Vue({
render: h => h(App),
beforeCreate(){
//安装安装全局事件总线
Vue.prototype.$bus = this;
}
}).$mount('#app')
使用事件总线
methods:{
demo(data){
...
}
}
.....
mounted(){
this.$bus.$on('pyy',this.demo)
}
提供数据
this.$bus.$emit('pyy',data)
将TodoList案例修改成全局事件总线方式
main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
beforeCreate(){
//安装安装全局事件总线
Vue.prototype.$bus = this;
}
}).$mount('#app')
App.vue
<template>
<div >
<div class="todo-container">
<div class="todo-wrap">
<MyHeader @addTodo="addTodo"></MyHeader>
<MyList :todos="todos" ></MyList>
<MyFooter :todos="todos" @checkedAll="checkedAll" @clearDoneTodo="clearDoneTodo"></MyFooter>
</div>
</div>
</div>
</template>
<script>
import MyHeader from './components/MyHeader.vue'
import MyFooter from './components/MyFooter.vue'
import MyList from './components/MyList.vue'
import { watch } from 'vue'
export default {
name:'App',
data() {
return {
//从本地读取数据
todos: JSON.parse(localStorage.getItem('todos')) || [],
}
},
components:{
MyHeader,MyFooter,MyList
},
methods:{
//添加方法:参数就是输入框所输入内容所在的对象
addTodo(todoObj){
//添加到集合的头部
this.todos.unshift(todoObj);
},
//修改方法,根据id去修改todo对象的done
changeTodo(id){
//遍历todos集合,找到与参数id一直的对象
this.todos.forEach((todo)=>{
//将该对象的done值取反
if(todo.id === id) todo.done = !todo.done
})
},
//删除方法,根据id删掉改对象
deleteTodo(id){
//过滤一下,将符合条件的留下,形成一个新的集合,并赋值给todos
this.todos = this.todos.filter((todo)=>{
return todo.id !== id;
})
},
//全选/全不选方法,参数就是,底部多选框的值
checkedAll(value){
//将参数赋值给数组中的每一个对象的done值
this.todos.forEach((todo)=>{
todo.done = value;
})
},
//过滤掉已经完成的
clearDoneTodo(){
this.todos = this.todos.filter((todo)=>{
return !todo.done
})
}
},
watch:{
todos:{
deep:true,
handler(value){
//本地存储,(存储到硬盘)
localStorage.setItem('todos',JSON.stringify(value))
}
}
},
mounted(){
this.$bus.$on('changeTodo',this.changeTodo);
this.$bus.$on('deleteTodo',this.deleteTodo)
},
//在销毁之前解绑,
beforeDestroy(){
this.$bus.$off('changeTodo');
this.$bus.$off('deleteTodo');
}
}
</script>
<style>
/*base*/
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>
MyList.vue
<template>
<ul class="todo-main">
<MyItem
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj" >
</MyItem>
</ul>
</template>
<script>
import MyItem from './MyItem.vue'
export default {
name:'MyList',
components:{
MyItem
},
props:['todos']
}
</script>
<style scoped>
/*main*/
.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>
MyItem.vue
<template>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="changeDone(todo.id)"/>
<span>{{todo.title}}</span>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)" >删除</button>
</li>
</template>
<script>
export default {
name:'MyItem',
data() {
return {
}
},
props:['todo'],
methods:{
//将修改的多选框对象的id作为参数
changeDone(id){
//使用全局事件总线
// this.changeTodo(id);
this.$bus.$emit('changeTodo',id);
},
//将删除的对象的id作为参数
handleDelete(id){
if(confirm('确定删除吗')){
//this.deleteTodo(id);
this.$bus.$emit('deleteTodo',id);
}
}
}
}
</script>
<style scoped>
/*item*/
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;
display: inline-block;
}
li:last-child {
border-bottom: none;
}
li:hover {
background-color: #dddddd;
}
li:hover button{
display: block;
}
</style>
MyHeader.vue
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model.trim="title" @keyup.enter="show"/>
</div>
</template>
<script>
import {nanoid} from 'nanoid'
export default {
name:'MyHeader',
data() {
return {
title:''
}
},
methods:{
show(){
//检查输入内容
if(this.title.trim() ==='' || this.title.trim() ==='null' || this.title.trim() ==='undefined') return alert("输入内容不能为空!null undefined ")
//将输入内容装进对象
const item = {id:nanoid(),title:this.title,done:false};
//调用添加方法
this.$emit('addTodo',item)
//清空输入框中内容
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>
MyFooter.vue
<template>
<div class="todo-footer" v-show="total">
<label>
<input type="checkbox" v-model="isAll"/>
</label>
<span>
<span>已完成{{completed}}</span> / 全部{{total}}
</span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
</div>
</template>
<script>
export default {
name:'MyFooter',
props:['todos'],
computed:{
total(){
return this.todos.length
},
completed(){
//循环遍历(第一个参数上一次循环返回的值,第二个参数,当前循环的对象)
return this.todos.reduce((pre,todo)=>{
//如果done为真,就加一
return pre + (todo.done ? 1 : 0);
},0)
},
//计算属性的set/get方法
isAll:{
get(){
//返回值布尔类型,判断是否全选
return this.total === this.completed && this.total > 0;
},
set(value){
//调用更改全部多选框的方法,将底部多选框的值传递进去
this.$emit('checkedAll',value)
}
}
},
methods:{
//调用App的清除方法
clearAll(){
this.$emit('clearDoneTodo')
}
}
}
</script>
<style scoped>
/*footer*/
.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>
消息订阅与发布
导入
import pubsub from ‘pubsub-js’
谁想接收消息,谁就订阅:
methods:{
demo(name,data){两个参数,第一个名字(pyy)第二个是传过来的数据
}
}
mounted(){
订阅消息,返回值是一个id,可以在销毁之前根据这个id取消订阅
this.pid = pubsub.subscribe(‘pyy’,this.demo)
}
发布消息(提供数据) : pubsub.publish(‘pyy’,数据)
取消订阅
pubsub.unsubscribe(this.pid );
给todoList案例添加编辑功能
(只修改了App.vue,MyItem.vue,MyHeader.vue)
在MyHeader.vue中只修改了一行,添加了isEdit属性,默认值是false
//将输入内容装进对象
const item = {id:nanoid(),title:this.title,done:false,isEdit:false};
App.vue
<template>
<div >
<div class="todo-container">
<div class="todo-wrap">
<MyHeader @addTodo="addTodo"></MyHeader>
<MyList :todos="todos" ></MyList>
<MyFooter :todos="todos" @checkedAll="checkedAll" @clearDoneTodo="clearDoneTodo"></MyFooter>
</div>
</div>
</div>
</template>
<script>
import MyHeader from './components/MyHeader.vue'
import MyFooter from './components/MyFooter.vue'
import MyList from './components/MyList.vue'
import pubsub from 'pubsub-js'
export default {
name:'App',
data() {
return {
//从本地读取数据
todos: JSON.parse(localStorage.getItem('todos')) || [],
}
},
components:{
MyHeader,MyFooter,MyList
},
methods:{
//添加方法:参数就是输入框所输入内容所在的对象
addTodo(todoObj){
//添加到集合的头部
this.todos.unshift(todoObj);
},
//修改方法,根据id去修改todo对象的done
changeTodo(id){
//遍历todos集合,找到与参数id一直的对象
this.todos.forEach((todo)=>{
//将该对象的done值取反
if(todo.id === id) todo.done = !todo.done
})
},
//删除方法,根据id删掉改对象
deleteTodo(_,id){
//过滤一下,将符合条件的留下,形成一个新的集合,并赋值给todos
this.todos = this.todos.filter((todo)=>{
return todo.id !== id;
})
},
//全选/全不选方法,参数就是,底部多选框的值
checkedAll(value){
//将参数赋值给数组中的每一个对象的done值
this.todos.forEach((todo)=>{
todo.done = value;
})
},
//过滤掉已经完成的
clearDoneTodo(){
this.todos = this.todos.filter((todo)=>{
return !todo.done
})
},
//修改方法
updateTodo(newTodo,value){
this.todos.forEach((todo)=>{
if(todo.id === newTodo.id){
todo.title = value;
return;
}
})
}
},
watch:{
todos:{
deep:true,
handler(value){
//本地存储,(存储到硬盘)
localStorage.setItem('todos',JSON.stringify(value))
}
}
},
mounted(){
this.$bus.$on('changeTodo',this.changeTodo);
this.$bus.$on('deleteTodo',this.deleteTodo);
this.$bus.$on('updateTodo',this.updateTodo);
//使用消息订阅
this.pubsubDeleteId = pubsub.subscribe('deleteItem',this.deleteTodo)
},
//在销毁之前解绑,
beforeDestroy(){
this.$bus.$off('changeTodo');
this.$bus.$off('deleteTodo');
this.$bus.$off('updateTodo');
//取消订阅
pubsub.unsubscribe(this.pubsubDeleteId);
}
}
</script>
<style>
/*base*/
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-edit {
color: #fff;
background-color: skyblue;
border: 1px solid rgb(63, 137, 167);
margin-right: 4px;
}
.btn-edit:hover {
color: #fff;
background-color: rgb(155, 214, 237);
}
.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>
MyItem.vue
<template>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="changeDone(todo.id)"/>
<span v-show="!todo.isEdit">{{todo.title}}</span>
<input type="text"
v-show="todo.isEdit"
:value="todo.title"
@blur="editEnd(todo,$event)"
@keyup.enter="editEnd(todo,$event)"
ref="editInput"
>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)" >删除</button>
<button class="btn btn-edit" @click="handleEdit(todo)" v-show="!todo.isEdit">编辑</button>
</li>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name:'MyItem',
data() {
return {
}
},
props:['todo'],
methods:{
//将修改的多选框对象的id作为参数
changeDone(id){
//使用全局事件总线
// this.changeTodo(id);
this.$bus.$emit('changeTodo',id);
},
//将删除的对象的id作为参数
handleDelete(id){
if(confirm('确定删除吗')){
//this.deleteTodo(id);
//this.$bus.$emit('deleteTodo',id);
pubsub.publish('deleteItem',id);
}
},
//编辑按钮触发方法
handleEdit(todo){
todo.isEdit = true;
//nextTick 下一轮在执行里面的内容
//自动获取焦点,如果不写这个,则第一轮都执行完了聚焦,再解析模板,那么聚焦就没有用了。
//等一轮,在聚焦。
this.$nextTick(function(){
this.$refs.editInput.focus();
})
},
//修改完毕 按回车或者失去焦点时触发
editEnd(todo,e){
todo.isEdit = false;
//如果修改内容为空,则弹窗
if(!e.target.value.trim()) return alert('not null');
this.$bus.$emit('updateTodo',todo,e.target.value)
}
},
}
</script>
<style scoped>
/*item*/
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;
display: inline-block;
}
li:last-child {
border-bottom: none;
}
li:hover {
background-color: #dddddd;
}
li:hover button{
display: block;
}
</style>
动画效果
作用:操作DOM元素的时候,在合适的时机给元素添加样式
App.vue
<template>
<div >
<Test></Test>
</div>
</template>
<script>
import Test from './components/Test.vue'
import 'animate.css'
export default {
name:'App',
components:{
Test
},
}
</script>
<style>
</style>
Test.vue
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<!-- 使用 transition 标签包裹要过渡的元素,,多个标签用transition-group 并且每个元素都要指定key值 -->
<transition-group
appear
name="animate__animated animate__bounce"
enter-active-class="animate__bounceIn"
leave-active-class="animate__bounceOut"
>
<h1 key="1" v-show="isShow">hello</h1>
<!-- <h1 key="2" v-show="!isShow">bye</h1> -->
</transition-group>
</div>
</template>
<script>
export default {
name:'Test',
data(){
return{
isShow:true
}
}
}
</script>
<style>
h1{
background-color: pink;
}
</style>
配置代理
vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave:false, /*关闭语法检查*/
devServer:{
proxy:{
'/pyy':{//匹配/pyy开头的路径
target:'http://localhost:5000',
pathRewrite:{'^/pyy':''},//请求时真实路径没有这个/pyy,将他去掉
changeOrigin:true//默认为true,是否开启 圆滑模式(狗头)
},
'/pj':{//匹配/pyy开头的路径
target:'http://localhost:5001',
pathRewrite:{'^/pj':''},//请求时真实路径没有这个/pyy,将他去掉
changeOrigin:true//默认为true,是否开启 圆滑模式(狗头)
}
}
}
})
App.vue
<template>
<div >
<button @click="getStudent">getStudentInfo</button>
<button @click="getCar">getCarInfo</button>
</div>
</template>
<script>
import axios from 'axios'
export default {
name:'App',
methods:{
getStudent(){
axios.get('http://localhost:8080/pyy/students').then(
response=>{
console.log('请求成功',response.data);
},
error=>{
console.log('请求失败',error.message);
},
)
},
getCar(){
axios.get('http://localhost:8080/pj/cars').then(
response=>{
console.log('请求成功',response.data);
},
error=>{
console.log('请求失败',error.message);
},
)
},
}
}
</script>
github案例
输入信息,去搜索github上的用户。
main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
beforeCreate(){
//安装安装全局事件总线
Vue.prototype.$bus = this;
}
}).$mount('#app')
App.vue
<template>
<div class="container">
<Search></Search>
<List></List>
</div>
</template>
<script>
import Search from './components/Search.vue'
import List from './components/List.vue'
export default {
name: "App",
components:{
Search,List
}
};
</script>
<style>
</style>
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="message"/> <button @click="Search">Search</button>
</div>
</section>
</template>
<script>
import axios from 'axios'
export default {
name:'Search',
data(){
return{
message:''
}
},
methods:{
Search(){
//将一个对象传递过去
this.$bus.$emit('searchUsers',{users:[],errMsg:'',isFirst:false,isLoading:true})
axios.get(`https://api.github.com/search/users?q=${this.message}`).then(
response=>{
//请求成功
this.$bus.$emit('searchUsers',{users:response.data.items,isLoading:false})
},
error=>{
//请求失败
this.$bus.$emit('searchUsers',{users:[].data.items,errMsg:error.message,isLoading:false})
},
)
}
}
}
</script>
<style>
</style>
List.vue
<template>
<div class="row">
<!-- 此处的a标签的href属性和img标签的src属性一定要动态绑定 -->
<div class="card" v-show="info.users.length" v-for="user in info.users" :key="user.login">
<a :href="user.html_url" target="_blank">
<img :src="user.avatar_url" style='width: 100px'/>
</a>
<p class="card-text">{{user.login}}</p>
</div>
<h2 v-show="info.isFirst">welcom use</h2>
<h2 v-show="info.isLoading">loading......</h2>
<h2 v-show="info.errMsg">{{info.errMsg}}</h2>
</div>
</template>
<script>
export default {
name:'List',
data(){
return{
info:{
users:[],
isFirst:true,
isLoading:false,
errMsg:''
}
}
},
methods:{
SearchUsers(infoObj){
//合并同类项,,将两个名字相同的项第二个覆盖第一个,如果第二个中没有第一个的某一项,则它不变
this.info = {...this.info,...infoObj}
}
},
mounted(){
this.$bus.$on('searchUsers',this.SearchUsers)
},
beforeDestroy(){
this.$bus.$off('searchUsers')
}
}
</script>
<style>
.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>
插槽
默认插槽
App.vue
<template>
<div class="container">
<Category title="游戏">
<!-- 组件标签里面的东西去填坑 -->
<ul>
<li v-for="(game,index) in games" :key="index">
{{game}}
</li>
</ul>
</Category>
<Category title="手机">
<img src="https://www.apple.com.cn/v/iphone-14-pro/c/images/overview/hero/hero_endframe__dtzvajyextyu_large.jpg" alt="">
</Category>
<Category title="电影">
<video controls src="https://www.bilibili.com/video/BV1Ds4y157xB?t=2.4"></video>
</Category>
</div>
</template>
<script>
import Category from './components/Category.vue'
export default {
name: "App",
components:{
Category
},
data(){
return {
games:['GTA5','20777','PUBG','SEKIO'],
phones:['apple','xiuaomi','HUAWEI','OPPO'],
movies:['肖申克的救赎','蜘蛛侠','蜘蛛侠2','蜘蛛侠3']
}
}
};
</script>
<style>
.container{
display: flex;
justify-content: space-around;
}
img{
width: 100%;
}
video{
width: 100%;
}
</style>
Category.vue
<template>
<div class="category">
<h3>{{title}}</h3>
<!-- 默认插槽 在这个地方挖个坑 -->
<slot>商品信息</slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title']
}
</script>
<style>
.category{
background-color: pink;
width: 200px;
height: 300px;
}
h3{
text-align: center;
background-color: skyblue;
}
</style>
具名插槽
App.vue
<template>
<div class="container">
<Category title="游戏">
<!-- 组件标签里面的东西去填坑 -->
<!-- slot用来和坑的名字对应,对应上了就去填坑 -->
<ul slot="conter">
<li v-for="(game, index) in games" :key="index">
{{ game }}
</li>
</ul>
<a slot="footer" href="https://www.wegame.com.cn/" target="_blank"
>wegame商店</a
>
</Category>
<Category title="手机">
<img
slot="conter"
src="https://www.apple.com.cn/v/iphone-14-pro/c/images/overview/hero/hero_endframe__dtzvajyextyu_large.jpg"
alt=""
/>
<div class="foot" slot="footer">
<a href="https://www.apple.com.cn/" target="_blank">apple</a>
<a href="https://www.huawei.com/" target="_blank">HUAWEI</a>
</div>
</Category>
<Category title="电影">
<video
slot="conter"
controls
src="https://www.bilibili.com/video/BV1Ds4y157xB?t=2.4"
></video>
<!-- v-slot:footer 这个东西只适用于 template标签-->
<template v-slot:footer >
<div class="foot">
<a href="https://www.bilibili.com/video/BV1Ds4y157xB?t=2.4" target="_blank"
>观看完整版</a
>
<a href="https://space.bilibili.com/9064879/?spm_id_from=333.999.0.0" target="_blank"
>进入up主页</a
>
</div>
<h4>紫雨carol</h4>
</template>
</Category>
</div>
</template>
<script>
import Category from "./components/Category.vue";
export default {
name: "App",
components: {
Category,
},
data() {
return {
games: ["GTA5", "20777", "PUBG", "SEKIO"],
phones: ["apple", "xiuaomi", "HUAWEI", "OPPO"],
movies: ["肖申克的救赎", "蜘蛛侠", "蜘蛛侠2", "蜘蛛侠3"],
};
},
};
</script>
<style>
a {
text-decoration: none;
color: black;
}
.container,
.foot {
display: flex;
justify-content: space-around;
}
img {
width: 100%;
}
video {
width: 100%;
}
h4{
text-align: center;
}
</style>
Category.vue
<template>
<div class="category">
<h3>{{title}}</h3>
<!-- 具名插槽 在这个地方挖个坑,起个名 -->
<slot name="conter">商品信息</slot>
<slot name="footer">商品信息</slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title']
}
</script>
<style>
.category{
background-color: pink;
width: 300px;
height: 400px;
}
h3{
text-align: center;
background-color: skyblue;
}
</style>
作用域插槽
App.vue
<template>
<div class="container">
<Category title="游戏">
<!-- 组件标签里面的东西去填坑 -->
<!-- slot-scope="{games} 传过来的是一个对象 解构赋值,用来接收坑里的数据 -->
<template slot-scope="{games}">
<ul >
<li v-for="(game, index) in games" :key="index">
{{ game }}
</li>
</ul>
</template>
</Category>
<Category title="游戏">
<template slot-scope="{games}">
<ol >
<li v-for="(game, index) in games" :key="index">
{{ game }}
</li>
</ol>
</template>
</Category>
<Category title="游戏">
<template slot-scope="{games}">
<h4 v-for="(game, index) in games" :key="index">
{{ game }}
</h4>
</template>
</Category>
</div>
</template>
<script>
import Category from "./components/Category.vue";
export default {
name: "App",
components: {
Category,
},
};
</script>
<style>
a {
text-decoration: none;
color: black;
}
.container,
.foot {
display: flex;
justify-content: space-around;
}
img {
width: 100%;
}
video {
width: 100%;
}
h4{
text-align: center;
}
</style>
Category.vue
<template>
<div class="category">
<h3>{{title}}</h3>
<!-- 作用域插槽 在这个地方挖个坑 数据也在这个坑里,谁填这个坑,用我的数据就行 -->
<slot :games="games">商品信息</slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title'],
data() {
return {
games: ["GTA5", "20777", "PUBG", "SEKIO"],
phones: ["apple", "xiuaomi", "HUAWEI", "OPPO"],
movies: ["肖申克的救赎", "蜘蛛侠", "蜘蛛侠2", "蜘蛛侠3"],
};
},
}
</script>
<style>
.category{
background-color: pink;
width: 300px;
height: 400px;
}
h3{
text-align: center;
background-color: skyblue;
}
</style>
VueX
专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理,适用于任意组件间通信。
vuex环境搭建
main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入index.js
import store from './store/index.js'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
store,
}).$mount('#app')
index.js
import Vue from 'vue'
//引入插件
import Vuex from 'vuex'
//使用插件
Vue.use(Vuex)
//用于存储数据
const state ={}
//用于操作数据
const mutations ={}
//用户相应组件中的动作
const actions ={}
//创建并导出vuex
export default new Vuex.Store({
state,mutations,actions
})
Vuex案例
App.vue
<template>
<div class="container">
<Count></Count>
</div>
</template>
<script>
import Count from './components/Count.vue'
export default {
name: "App",
components: {
Count,
},
mounted(){
console.log(this)
}
};
</script>
<style>
</style>
Count.vue
<template>
<div>
<!-- 获取到index.js中states里面的sum属性的值 -->
<h2>当前的值是:{{$store.state.sum}}</h2>
<select v-model.number="num">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="addition">加</button>
<button @click="subtraction">减</button>
<button @click="oddAdd">奇数加</button>
<button @click="waitforHasband">等一下,我老公呢</button>
</div>
</template>
<script>
export default {
name: "Count",
data() {
return {
num: 1,
};
},
methods: {
addition() {
//可以通过commit直接调用mutations,略过actions
this.$store.commit("ADD", this.num);
},
subtraction() {
this.$store.commit("SUB", this.num);
},
oddAdd() {
//通过dispatch调用Action,方法名为add 参数是n
this.$store.dispatch("odd", this.num);
},
waitforHasband() {
this.$store.dispatch("wait", this.num);
},
},
};
</script>
<style>
</style>
index.js
import Vue from 'vue'
//引入插件
import Vuex from 'vuex'
//使用插件
Vue.use(Vuex)
//用于存储数据
const state = {
//声明一个属性,默认值为0
sum: 0
}
//用户相应组件中的动作
const actions = {
//接收dispatch的调用,接收两个参数,第一个是阉割版的stroe,第二个是传过来的值
odd(context, value) {
if (context.state.sum % 2) {
context.commit('ADD', value);
}
},
wait(context, value) {
//调用commit,将数据传递给mutations
setTimeout(() => {
context.commit('ADD', value);
}, 500)
},
}
//用于操作数据
const mutations = {
//用于接收commit传过来的值,第一个参数是对象,包含了states里面声明的属性,第二个参数是传递过来的值
ADD(state, value) {
//修改states里面的sum属性
state.sum += value;
},
SUB(state, value) {
state.sum -= value;
},
}
//创建并导出vuex
//创建并导出vuex
export default new Vuex.Store({
state, mutations, actions
})
当state中的数据需要进行加工时,可以使用getters
const getters = {
bigSum(state){
return state.sum;
}
}
//创建并导出vuex
//创建并导出vuex
export default new Vuex.Store({
state, mutations, actions,getters
})
在组件中读取数据时:
$store.getters.bigSum
mapState、mapGetters、mapActions、mapMutations
修改上方案例写法用这四个去写,只修改了一个文件
Count.vue
<template>
<div>
<!-- 获取到index.js中states里面的sum属性的值 -->
<h2>当前的值是:{{sum}}</h2>
<h2>当前的值的九倍是:{{bigSum}}</h2>
<h2>我是谁?{{name}}</h2>
<h2>我在那?{{address}}</h2>
<select v-model.number="num">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="addition(num)">加</button>
<button @click="subtraction(num)">减</button>
<button @click="oddAdd(num)">奇数加</button>
<button @click="waitforHasband(num)">等一下,我老公呢</button>
</div>
</template>
<script>
import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'
export default {
name: "Count",
data() {
return {
num: 1,
};
},
methods: {
// addition() {
// //可以通过commit直接调用mutations,略过actions
// this.$store.commit("ADD", this.num);
// },
// subtraction() {
// this.$store.commit("SUB", this.num);
// },
//借助mapMutations向mutations里面传递,要在组件里这么写addition(参数)
//参数为对象
...mapMutations({addition:'ADD',subtraction:'SUB'}),
//参数为数组
//...mapMutations(['ADD','SUB']),
// oddAdd() {
// //通过dispatch调用Action,方法名为add 参数是n
// this.$store.dispatch("odd", this.num);
// },
// waitforHasband() {
// this.$store.dispatch("wait", this.num);
// },
//借助mapActions向actions里面传递,要在组件里这么写addition(参数) 参数为对象
...mapActions({oddAdd:'odd',waitforHasband:'wait'})
//数组为参数的写法,前后名字相同才可以这么写
//...mapActions(['odd','wait'])
},
computed:{
//因为返回值是一个对象,computed也是一个对象,对象里不能有对象,所以用...将里面内容摊开。
//借助mapState从state中获取数据,参数为对象
//...mapState({sum:'sum',name:'name',address:'address'}),
//借助mapState从state中获取数据,参数为数组(state里面的属性名和组件里面的属性名相同时才能这么写)
...mapState(['sum','name','address']),
//借助mapGetters从state中获取数据,参数为对象
//...mapGetters({bigSum:'bigSum'}),
//借助mapGetters从state中获取数据,参数为对象
...mapGetters(['bigSum'])
}
};
</script>
<style>
</style>
多组件共享数据
模块化+命名空间
index.js
import Vue from 'vue'
//引入插件
import Vuex from 'vuex'
//使用插件
Vue.use(Vuex)
//模块化,将数据管理分成一块一块
const CountStore = {
//开启命名空间
namespaced:true,
state:{
sum: 0,
name:'pyy',
address:'earth',
},
actions:{
//接收dispatch的调用,接收两个参数,第一个是阉割版的stroe,第二个是传过来的值
odd(context, value) {
if (context.state.sum % 2) {
context.commit('ADD', value);
}
},
wait(context, value) {
//调用commit,将数据传递给mutations
setTimeout(() => {
context.commit('ADD', value);
}, 500)
},
},
mutations:{
//用于接收commit传过来的值,第一个参数是对象,包含了states里面声明的属性,第二个参数是传递过来的值
ADD(state, value) {
//修改states里面的sum属性
state.sum += value;
},
SUB(state, value) {
state.sum -= value;
},
},
getters:{
bigSum(state){
return state.sum * 9;
}
},
}
const PersonStore = {
namespaced:true,
state:{
dogs:[{id:'1001',name:'张三'},{id:'1002',name:'李四'}]
},
actions:{
addPang(context,value){
if(value.name.indexOf('庞') === 0){
context.commit('ADD_DOG',value)
}else{
alert("你不姓庞,滚")
}
}
},
mutations:{
ADD_DOG(state,value){
state.dogs.unshift(value);
}
},
getters:{
firstNameP(state){
return state.dogs[0].name
}
},
}
//创建并导出vuex
//创建并导出vuex
export default new Vuex.Store({
//模块化引入方式,一定要写在moudules里面
modules:{
countAbout:CountStore,
personAbout:PersonStore
}
})
App.vue
<template>
<div class="container">
<Count></Count>
<hr>
<Person></Person>
</div>
</template>
<script>
import Count from './components/Count.vue'
import Person from './components/Person.vue'
export default {
name: "App",
components: {
Count,Person
},
mounted(){
console.log(this)
}
};
</script>
<style>
</style>
Count.vue
<template>
<div>
<!-- 获取到index.js中states里面的sum属性的值 -->
<h2>当前的值是:{{sum}}</h2>
<h2>当前的值的九倍是:{{bigSum}}</h2>
<h2>我是谁?{{dogs[i].name}}</h2>
<button @click="(i=(i+1)%dogs.length)">下一位</button>
<h2>我在那?{{address}}</h2>
<select v-model.number="num">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="addition(num)">加</button>
<button @click="subtraction(num)">减</button>
<button @click="oddAdd(num)">奇数加</button>
<button @click="waitforHasband(num)">等一下,我老公呢</button>
</div>
</template>
<script>
import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'
export default {
name: "Count",
data() {
return {
num: 1,
i:0
};
},
methods: {
//命名空间开启后第一个参数就是在配置moudles里的模块的名字
...mapMutations('countAbout',{addition:'ADD',subtraction:'SUB'}),
...mapActions('countAbout',{oddAdd:'odd',waitforHasband:'wait'})
},
computed:{
...mapState('countAbout',['sum','name','address']),
...mapState('personAbout',['dogs']),
...mapGetters('countAbout',['bigSum'])
}
};
</script>
<style>
</style>
Person.vue
<template>
<div>
<h2>备胎列表</h2>
<h4>Count组件求和:{{sum}}</h4>
<h4>下一个舔狗是:{{firstNameP}}</h4>
<input type="text" placeholder="请输入名字" v-model="dogName">
<button @click="addDog">添加备胎</button>
<button @click="addDogP">你姓庞吗?</button>
<ul>
<li v-for="dog in dogs" :key="dog.id">
{{dog.name}}
</li>
</ul>
</div>
</template>
<script>
import {mapState,mapActions,mapGetters,mapMutations} from 'vuex'
import {nanoid} from 'nanoid'
export default {
name:'Person',
data(){
return{
dogName:'',
}
},
computed:{
...mapState('personAbout',['dogs']),
...mapState('countAbout',['sum']),
...mapGetters('personAbout',['firstNameP'])
},
methods:{
addDog(){
const dog = {id:nanoid(),name:this.dogName};
this.$store.commit('personAbout/ADD_DOG',dog);
this.dogName = '';
},
addDogP(){
const dog = {id:nanoid(),name:this.dogName};
this.$store.dispatch('personAbout/addPang',dog);
this.dogName = '';
}
}
}
</script>
<style>
</style>
路由
路由的基本使用
通常情况下路由组件放在pages文件夹下,一般组件放在components文件夹下
main.js
//引入Vue
import Vue from 'vue'
import VueRouter from 'vue-router'
//引入App
import App from './App.vue'
import router from './router/index.js'
Vue.config.productionTip = false
Vue.use(VueRouter)
new Vue({
render: h => h(App),
router:router
}).$mount('#app')
index.js
import VueRouter from 'vue-router'
import Home from '../pages/Home.vue'
import About from '../pages/About.vue'
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
})
App.vue
<template>
<div>
<div class="row">
<Banner></Banner>
</div>
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<!-- <a class="list-group-item" href="./about.html">About</a>
<a class="list-group-item active" href="./home.html">Home</a> -->
<!-- 用 router-link标签 实现路由的切换,to 目标路由-->
<router-link class="list-group-item" active-class="active" to="/about">About</router-link>
<router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
</div>
</div>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body">
<!-- 指定组件呈现的位置 -->
<router-view></router-view>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Banner from './components/Banner.vue'
export default {
name: "App",
components: {
Banner
},
};
</script>
<style>
</style>
About.vue
<template>
<h2>我是About的内容</h2>
</template>
<script>
export default {
name:'About'
}
</script>
<style>
</style>
Home.vue
<template>
<h2>我是Home的内容</h2>
</template>
<script>
export default {
name:'Home'
}
</script>
<style>
</style>
Banner.vue
<template>
<div class="col-xs-offset-2 col-xs-8">
<div class="page-header"><h2>Vue Router Demo</h2></div>
</div>
</template>
<script>
export default {
name:'Banner'
}
</script>
<style>
</style>
嵌套路由和路由的query参数
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home,
//二级路由,一个路由下的子路由
children:[
{
//直接写名字就行,自动加/home/
path:'message',
component:Message,
children:[
{
path:'detail',
component:Detail
}
]
},
{
path:'news',
component:News
},
]
},
]
})
Message.vue
<template>
<div>
<ul>
<li v-for="m in messages" :key="m.id">
<!-- 传递参数,query内容就是参数 path是路径 -->
<router-link :to="{
path:'/home/message/detail',
query:{
id:m.id,
title:m.title
}
}">{{m.title}}</router-link>
</li>
</ul>
<hr>
<router-view></router-view>
</div>
</template>
<script>
export default {
name:'Message',
data(){
return{
messages:[
{id:'001',title:'消息001'},
{id:'002',title:'消息002'},
{id:'003',title:'消息003'}
]
}
}
}
</script>
<style>
</style>
Detail.vue
<template>
<ul>
<!-- 接收参数 -->
<li>消息编号:{{$route.query.id}}</li>
<li>消息标题:{{$route.query.title}}</li>
</ul>
</template>
<script>
export default {
name:'Detail'
}
</script>
<style>
</style>
命名路由
简化跳转
{
//配置项名称
name:'pyy',
component:Message,
children:[
{
path:'detail',
component:Detail
}
]
},
<router-link :to="{
//这里就可以直接写名称了不用再写 xx/xxx/xx
name:'pyy',
query:{
id:m.id,
title:m.title
}
}">{{m.title}}</router-link>
params参数
{
//配置项名称
name:'pyy',
component:Message,
children:[
{
name:'pj',
path:'detail/:id/:title',//使用占位符声明接收params参数
component:Detail
}
]
},
两种写法
<router-link :to="/home/message/detail/${m.id}/${m.title}">{{m.title}}</router-link>
<router-link :to="{
//使用params时,这里一定要写name属性,不能写path
name:'pyy',
params:{
id:m.id,
title:m.title
}
}">{{m.title}}</router-link>
接收参数
<li>消息编号:{{$route.params.id}}</li>
props配置
让路由组件更加方便的收到参数。
{
//直接写名字就行,自动加/home/
path:'message',
component:Message,
children:[
{
path:'detail',
component:Detail,
//第一种写法,对象写法,通过key-value的组合传递
//props:{a:'pyy'}
//第二种写法,布尔值,接收到所有params参数
//props:true,
//第三种写法,函数,通过$route 取到数据通过ey-value的组合传递
//这里的参数route,接收到的就是$route里面就有数据
props(route){
return{
id:route.query.id,
title:route.query.title
}
}
}
]
},
编程式路由导航
不需要这个标签了
methods: {
//push的方法,浏览器会记录每一次访问的地址,可以依次退回查看
pushShow(m) {
console.log(m)
this.$router.push({
name: "ddd",
query: {
id:m.id,
title:m.title,
},
});
},
//replace的方法,浏览器只会记录最近一次的访问地址,之前的都被覆盖了。
replaceShow(m) {
this.$router.replace({
name: "ddd",
query: {
id: m.id,
title: m.title,
},
});
},
},
浏览器前进后退
methods:{
//后退一步
back(){
this.$router.back();
},
//前进一步
forward(){
this.$router.forward();
},
//
gogogo(x){
//参数为数字,正数就是前进几步,负数就是后退几步。
this.$router.go(x);
}
}
缓存路由组件
让不展示的路由组件保持挂载,不被销毁。
被这个标签包裹的就会保持活力。
include内容填写组件名,就是只对这个组件保持活力
<!--缓存一个-->
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
<!--缓存多个-->
<keep-alive :include="['News','Message']">
<router-view></router-view>
</keep-alive>
路由的独有的声明周期
activated被激活时触发
deactivated 失活时触发
activated(){
this.timer = setInterval(()=>{
console.log("闪")
this.opacity-=0.01
if(this.opacity<=0) this.opacity = 1
},16)
},
deactivated(){
clearInterval(this.timer)
}
全局前置路由守卫
const router = new VueRouter({
routes:[
{
name:'gy',
path:'/about',
component:About
},
{
name:'jia',
path:'/home',
component:Home,
//二级路由,一个路由下的子路由
children:[
{
//直接写名字就行,自动加/home/
name:'xx',
path:'message',
component:Message,
meta:{isAuth:true},
children:[
{
name:'ddd',
path:'detail',
component:Detail,
//第一种写法,对象写法,通过key-value的组合传递
//props:{a:'pyy'}
//第二种写法,布尔值,接收到所有params参数
//props:true,
//第三种写法,函数,通过$route 取到数据通过ey-value的组合传递
//这里的参数route,接收到的就是$route里面就有数据
props(route){
return{
id:route.query.id,
title:route.query.title
}
}
}
]
},
{
name:'xw',
path:'news',
component:News,
meta:{isAuth:true},//配置自定义属性,在meta里面可以自定义属性,
},
]
},
]
})
//前置守卫(大树守卫),进去之前我先检查一下子。一开始调用一次
//第一个参数,到哪里去
//第二个参数,从哪里来
//第三个参数,让不让过去
router.beforeEach((to,from,next)=>{
//是否需要校验一下 name就是路由配置时的name属性
if(to.meta.isAuth){
if(localStorage.getItem('name') === 'pyy'){
next()
}else{
alert('你的名字不对')
}
}
//其他模块随便进
else{
next()
}
})
export default router;
后置路由守卫
//后置守卫,进去之后触发
router.afterEach((to,from)=>{
//进去之后改变网页标题
document.title = to.meta.title || '庞杰'
})
独享路由守卫
{
name: 'xw',
path: 'news',
component: News,
meta: { isAuth: true, title: '新闻' },//配置自定义属性,在meta里面可以自定义属性,
//独享路由守卫,只在进入这个路由组件时触发
beforeEnter: (to, from, next) => {
//是否需要校验一下 name就是路由配置时的name属性
if (to.meta.isAuth) {
if (localStorage.getItem('name') === 'pyy') {
next()
} else {
alert('你的名字不对')
}
}
//其他模块随便进
else {
next()
}
}
},
组件内路由守卫
在组件内写的方法。
export default {
name:'About',
//进入守卫,通过路由规则进入该组件才会被调用
beforeRouteEnter(to,from,next){
},
//离开守卫,通过路由规则离开该组件才会被调用
beforeRouteLeave (to, from, next) {
// ...
}
}
history模式hash模式
hash模式:带着#,不美观,兼容性好
history模式:地址干净,美观,兼容性略差,部署上线后需要后端配合解决404问题
Vue3
main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
setup(拉开序幕,vue3新的配置项)
所有组合API的表演舞台
组件中所用到的数据,方法等等,都要配置在setup中
若返回一个对象,则对象中的属性,方法,在模板中都可以直接用
setip不能是async函数,因为返回值不再是return的对象,而是Promise,模板看不到rerurn对象中的属性了!!!
但是如果需要返回一个Prommise实例时,可以配合Suspense和异步组件使用
初始vue3
App.vue
<template>
<h1>vue3</h1>
<h2>who am i? {{name}}</h2>
<h2>i am old? {{age}}</h2>
<button @click="sayHello" >speack</button>
</template>
<script>
export default {
name: 'App',
setup(){
//声明属性
let name = 'pyy';
let age = 24;
//函数
function sayHello(){
alert('HelloWord')
}
//将属性和函数返回出去,模板才能用
return {name,age,sayHello}
}
}
</script>
<style>
</style>
ref函数
作用:定义一个响应式数据
App.vue
<template>
<h1>vue3</h1>
<h2>who am i? {{name}}</h2>
<h2>i am old? {{age}}</h2>
<h2>my wife? {{wife.name}} {{wife.age}}</h2>
<button @click="sayHello" >speack</button>
<button @click="changeWife" >change</button>
</template>
<script>
import {ref} from 'vue'
export default {
name: 'App',
setup(){
//声明属性
//使用ref函数声明属性时,会返回一个RefImpl对象,里面有个value属性,
let name = ref('pyy');
let age = ref(24);
//使用ref函数声明对象时,会返回一个RefImpl对象,里面有个value属性,value属性为一个Proxy对象,该对象里包含的就是声明对象中的内容。
let wife = ref({name:'zq',age:24});
//函数
function sayHello(){
alert('HelloWord')
}
function changeWife(){
//修改name属性值,需要修改其RefImpl对象内的value属性,才能使其打到响应式
// name.value = 'pj';
// console.log(name)
//修改对象内部属性时,需要修改其RefImpl对象内的value属性(Proxy对象)下的内部属性,才能使其完成响应式修改
wife.value.name = 'zqq'
console.log(wife.value)
}
//将属性和函数返回出去,模板才能用
return {name,age,sayHello,wife,changeWife}
}
}
</script>
<style>
</style>
reactive函数
作用:定义一个对象类型的响应式数据(基本类型不要用这个,还是用ref)
他可以是深层次的,就是可以一层套一层。
App.vue
<template>
<h1>vue3</h1>
<h2>who am i? {{name}}</h2>
<h2>i am old? {{age}}</h2>
<h2>hobby {{hobby}}</h2>
<h2>my wife? {{wife.name}} {{wife.age}}</h2>
<button @click="sayHello" >speack</button>
<button @click="changeWife" >change</button>
</template>
<script>
import {ref, reactive} from 'vue'
export default {
name: 'App',
setup(){
//声明属性
//使用ref函数声明属性时,会返回一个RefImpl对象,里面有个value属性,
let name = ref('pyy');
let age = ref(24);
//使用reactive函数声明对象时,直接是一个Proxy对象,该对象里包含的就是声明对象中的内容。
let wife = reactive({name:'zq',age:24});
let hobby = reactive(['eat','play','study']);
//函数
function sayHello(){
alert('HelloWord')
}
function changeWife(){
//直接修改其内部属性
wife.name = 'zqq';
console.log(wife);
//直接通过数组索引修改值
hobby[2] = 'watch'
}
//将属性和函数返回出去,模板才能用
return {name,age,sayHello,wife,changeWife,hobby}
}
}
</script>
<style>
</style>
vue3的响应式
原理:
通过Proxy(代理)拦截对象中任意属性的变化,包括:属性值的读写,属性的添加,删除
通过Reflect(反射):对被代理的属性进行操作。
setip注意点:
1,在beforecreate生命周期之前执行
2,两个参数(props,context)
props:值为对象,组件外部传递进来并且组件内部声明接收的属性
context:上下文对象其包含三个关键字:
(1):attrs 值为对象,包含组件传递过来的但是没有在props配置中声明的属性,就会被流放至此。
(2):slots:接收到的插槽内容。
(3):emit 分发自定义事件的函数,
Demo.vue
<template>
<h2>who am i? {{ person.name }}</h2>
<h2>i am old? {{ person.age }}</h2>
<button @click="hi">hi</button>
</template>
<script>
import { reactive } from "vue";
export default {
name: "Demo",
//接收父类的值
props: ["msg", "school"],
emits:['hello'],
//props参数是一个Proxy代理对象。里面就是父组件传递过来的值 Proxy(Object) {msg: 'pyy', school: 'zhonghuan'}
//context上下文包含接收自定义事件的关键字 emit 兜底的 attrs 插槽 slots
setup(props, context) {
let person = reactive(
{
name: "张三",
age: 44,
},
);
//
function hi() {
context.emit("hello", 666);
}
console.log(props);
console.log(context.attrs);
console.log(context.emit);//自定义事件
console.log(context.slots);//父组件的插槽
return {
person,
hi,
};
},
};
</script>
<style>
</style>
App.vue
<template>
<Demo @hello="say" msg='pyy' school='zhonghuan'>
<template v-slot:qwer>
<span >
1233
</span>
</template>
</Demo>
</template>
<script>
import Demo from './components/Demo.vue'
export default {
name: 'App',
components:{
Demo
},
setup(){
function say(value){
alert(`good morning${value}`)
}
return {
say
}
}
}
</script>
<style>
</style>
计算属性computed
与vue2中的计算属性功能一样
Demo.vue
<template>
<h3>{{ person.firstName }}</h3>
<h3>{{ person.lastName }}</h3>
<input type="text" v-model="person.firstName" /><br />
<br />
<input type="text" v-model="person.lastName" /><br />
<br />
<input type="text" v-model="fullName" />
</template>
<script>
import { reactive, computed } from "vue";
export default {
name: "Demo",
setup() {
let person = reactive({
firstName: "张",
lastName: "三",
});
//简写 只能读,不能改
let fullName2 = computed(()=>{
return person.firstName + "-" + person.lastName;
})
//完整版写法
let fullName = computed({
get() {
return person.firstName + "-" + person.lastName;
},
set(value) {
let name = value.split("-");
person.firstName = name[0];
person.lastName = name[1];
},
});
return {
person,
fullName,
};
},
};
</script>
<style>
</style>
watch属性
和vue2的监视属性功能一样
监视recative定义的响应式数据时:oldValue无法正确获取,(deep配置失效)
监视recactive定义的响应式数据中某个属性时deep配置生效
<template>
<h3>{{ person.firstName }}</h3>
<h3>{{ person.lastName }}</h3>
<h3>{{ age }}</h3>
<button @click="age++">age++</button>
<input type="text" v-model="person.firstName" /><br />
<br />
<input type="text" v-model="person.lastName" /><br />
<br />
<input type="text" v-model="fullName" />
</template>
<script>
import {ref, reactive, computed ,watch} from "vue";
export default {
name: "Demo",
setup() {
let age = ref(12);
let person = reactive({
firstName: "张",
lastName: "三",
});
//第一个参数,监视的属性名
//第二个参数,回调函数,
//第三个参数,配置项{immediate:true,deep:true}
//第一种情况,监视ref
watch(age,(newValue,oldValue)=>{
console.log("年龄发生了变化",newValue,oldValue)
})
//第二种情况,监视reactive
watch(person,(newValue,oldValue)=>{
console.log("名字发生了变化",newValue,oldValue)
})
//第三种情况,同时监视数组中的元素
watch([age,person],(newValue,oldValue)=>{
console.log("名字或年龄发生了变化",newValue,oldValue)
})
//第四种情况,监视reactive响应式数据中的某个属性
watch(()=>person.firstName,(newValue,oldValue)=>{
console.log("姓发生了变化",newValue,oldValue)
})
return {
age,
person,
};
},
};
</script>
<style>
</style>
watchEffect函数
不用指定监视那个属性,在该回调中用到谁就监视谁。
watchEffect(()=>{
const a1 = age.value;
const s2 = person.firstName
console.log("watchEffech监视到有属性变化了")
})
生命周期
setup函数就代表了beforeCreate 和created这两个生命周期钩子
<template>
<h3>{{ age }}</h3>
<button @click="age++">age++</button>
</template>
<script>
import {ref, onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount, onUnmounted} from "vue";
export default {
name: "Demo",
setup() {
let age = ref(24);
//组合式API的方式写生命周期钩子
onBeforeMount(()=>{
console.log('----onBeforeMount')
})
onMounted(()=>{
console.log('----onMounted')
})
onBeforeUpdate(()=>{
console.log('----onBeforeUpdate')
})
onUpdated(()=>{
console.log('----onUpdated')
})
onBeforeUnmount(()=>{
console.log('----onBeforeUnmount')
})
onUnmounted(()=>{
console.log('----onUnmounted')
})
return {
age
};
},
};
</script>
<style>
</style>
自定义hook函数
本质是一个函数,把setup里面用到的CompositionAPI(组合式API)进行封装
类似于Vue2中的mixin
优势:复用代码,让setup中的逻辑更清楚
toRef
作用:创建一个ref对象,其value值指向另一个对象中的某个属性。
语法:const name = toRef(person,‘name’)
应用:将响应式对象中的某个属性单独提供给外部使用时。
扩展:toRefs与toRef功能一致,但可以批量创建多个ref对象,toRefs(person)
<template>
<h3>{{ person }}</h3>
<h3>{{ name }}</h3>
<h3>{{ age }}</h3>
<h3>{{ job.j1.salary }}k</h3>
<button @click="age++">age++</button>
<button @click="job.j1.salary++">salary++</button>
</template>
<script>
import {reactive, toRef,toRefs} from "vue";
export default {
setup() {
let person = reactive( {
name:'pyy',
age:18,
job:{
j1:{
salary:24
}
}
})
return {
person,
...toRefs(person),
// name:toRef(person,'name'),
// age:toRef(person,'age'),
// salary:toRef(person.job.j1,'salary'),
};
},
};
</script>
<style>
</style>
其他Composition API
shallowReactive与shallRef
shallowReactive只处理对象最外层属性的响应式
shallRef值处理基本数据类型的响应式,不处理对象类型的响应式
readonly与shallReadonly
readonly:让一个响应式数据变为只读的(深只读)
shallReadonly:让一个响应式数据变为只读的(浅只读)
toRaw与markRaw
toRaw:将一个由reactive生成的响应式对象转为普通对象
使用场景:用于读取响应式对象对应的普通对象,对着普通对象的操作不会引起页面的更新
markRaw:标记一个对象,使其永远不会成为一个响应式对象
customRef
创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制
<template>
<h3>{{ keyword }}</h3>
<input type="text" v-model="keyword">
</template>
<script>
import {customRef, reactive, toRef,toRefs} from "vue";
export default {
name:'App',
setup() {
function myRef(value,delay){
let timer;
//track追踪value的变化,
//trigger 通知vue去解析模板
return customRef((track,trigger)=>{
return{
get(){
track();
return value;
},
set(newValue){
//延时一下再去更新h3中的内容,防抖
clearTimeout(timer)
timer = setTimeout(() => {
value = newValue;
trigger();
}, delay);
}
}
})
}
let keyword = myRef('123',1000);//使用自定义的ref
return {
keyword
};
},
};
</script>
<style>
</style>
provide与inject
实现祖宗组件与 后代组件之间通信
实例:祖宗=App.vue 后代(隔代)= Sons.vue
App.vue
<template>
<div class="app">
<h4>app</h4>
<h4>{{name}}</h4>
<h4>{{salary}}</h4>
<Child></Child>
</div>
</template>
<script>
import Child from './components/Child.vue'
import {reactive,provide,toRefs} from 'vue'
export default {
name: 'App',
components:{Child},
setup() {
let car = reactive({
name:'bmw',
salary:40
})
//使用che名字留给后代组件用定义的car对象
provide('che',car);
return {
...toRefs(car)
}
}
}
</script>
<style>
.app{
background: gray;
padding: 10px;
}
</style>
Child.vue
<template>
<div class="child">
<h4>child</h4>
<Son></Son>
</div>
</template>
<script>
import Son from './Sons.vue';
export default {
name: "Child",
components:{Son}
};
</script>
<style>
.child {
background: green;
padding: 10px;
}
</style>
Sons.vue
<template>
<div class="son">
<h4>Son</h4>
<h4>{{name}}</h4>
<h4>{{salary}}</h4>
</div>
</template>
<script>
import {inject, toRefs} from 'vue'
export default {
name:'Son',
setup() {
let car = inject('che')
return {
...toRefs(car)
}
}
}
</script>
<style>
.son{
background: pink;
padding: 10px;
}
</style>
响应式数据判断
isRef:是否为ref对象
isReacvice:是否为reactive创建的响应式代理
isReadonly:是否为readonly创建的只读代理
isProxy:是否为一个代理对象
新的组件
Fragment
根标签,在vue3中可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
减少标签层级,减小内存占用
Teleport(TP传送)
用标签包裹的内容会被传送到to=“xxx”的位置
<template>
<button @click="isShow = true">打开弹窗</button>
<teleport to='body' >
<div class="mask" v-if="isShow">
<div class="dialog">
<h4>弹窗</h4>
<h4>弹窗</h4>
<h4>弹窗</h4>
<h4>弹窗</h4>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>
</template>
<script>
import {ref} from 'vue'
export default {
name:'Dialog',
setup() {
let isShow = ref(false)
return{isShow}
}
}
</script>
<style>
.mask{
background: rgba(0, 0, 0, 50%);
position: absolute;
left: 0;top: 0;right: 0;bottom: 0;
}
.dialog{
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
text-align: center;
height: 300px;
width: 300px;
background: skyblue;
}
</style>
Suspense
异步加载组件