一、Vue简介

1. Vue特点

  • 采用组件化模式,提高代码复用率让代码更好维护
  • 声明式编码,无需操作DOM,提高开发效率
  • 使用虚拟DOM+Diff算法,尽量复用DOM节点

2. 起步

初学者不建议使用vue-cli安装,请用script标签引入

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

下载地址

  • 开发版本:https://cn.vuejs.org/js/vue.js
  • 生产版本:https://cn.vuejs.org/js/vue.min.js

3. 初识

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>Vue学习</title>
		<script src="./js/vue.js"></script>
	</head>
	<body>
		<div id="root">
			<h2>{{username}}</h2>
			<h2>{{nickname}}</h2>
			<h2>{{age}}</h2>
		</div>
		<script type="text/javascript">
			Vue.config.productionTip = false;
			new Vue({
				el:"#root" ,//绑定页面DOM
				data:{
					username:"zhiyuan",
					nickname:"絷缘",
					age:26
				},
			});
		</script>
	</body>
</html>

Vue中一个实例对应一个页面DOM容器,一个DOM容器无法对应多个实例,一个实例也不能绑定多个DOM容器

4. 模板语法

4.1 插值语法(插值表达式)

①. 插值表达式
  • 格式:{{ 表达式 }}

注意:插值表达式中必须是JS代码或JS表达式或Vue实例中声明的data属性

4.2 指令语法(属性绑定)

②. 属性绑定
  • 格式:v-bind:[attributeName]

    • <div id="root">
      	<img v-bind:src="avatar"/>    
      </div>
      <script type="text/javascript">
      	new Vue({
             el:"#root",
             data:{
             	   avatar:"https://v1.alapi.cn/api/avatar?email=zhiyuandnc666@gmail.com&size=100"	    
             } 
          });
      </script>
      
    • 页面效果

    页面效果
  • 简写::[attributeName]

    • <div id="root">
      	<img :src="avatar"/>
      </div>
      <script type="text/javascript">
      	new Vue({
             el:"#root",
             data:{
             	   avatar:"https://v1.alapi.cn/api/avatar?email=zhiyuandnc666@gmail.com&size=100"	    
             } 
          });
      </script>
      
②. 双向绑定
  • 格式:v-model:value

    • <div id="root">
      	<img :src="avatar"/>    
          <input type="text" v-model:value="avatar"/>
      </div>
      <script type="text/javascript">
      	new Vue({
             el:"#root",
             data:{
             	   avatar:"https://v1.alapi.cn/api/avatar?email=zhiyuandnc666@gmail.com&size=100"	    
             } 
          });
      </script>
      

    如下代码是错误的,因为v-model只能应用表单类元素上,我们认为只有可以被改变value的元素双向绑定才有意义

  • 简写:v-model

    • v-model默认绑定就是value属性

5. 容器绑定两种方式

5.1 方式一:el绑定

new Vue({
   	el:"DOM元素标志",//使用el挂载DOM容器
    data:{
    	key:value,
        ......
    }
});

5.2 方式二:mount挂载

const vm = new Vue({
   data:{
       key:value,
       ......
   } 
});
vm.$mount('DOM元素标志');//使用实例对象挂载DOM容器

6. data属性的两种写法

6.1 方式一:对象形式

new Vue({
   	el:"#container",
    data:{
        key1:value1,
        key2:value2,
        key...:value...
    }
});

6.2 方式二:函数形式

new Vue({
    el:"#container",
    data:function(){
        return {
            key1:value1,
            key2:value2,
            key...:value...
        }
    }
    //简写
    data(){
    	return {
            key1:value1,
            key2:value2,
            key...:value...
        }
	}
});

注意:函数方式返回data数据时,不可使用箭头函数,因为箭头函数没有自己的this对象,会向上寻找父类的this,就会拿到window对象,但是我们书写返回函数需要被Vue对象所调用,故而不能使用箭头函数

应用场景:组件封装时,我们必须使用函数式写法返回data值

二、MVVM简介

1. MVVM模型组成

1.1 M:模型(Model)

  • 对应data中的数据

1.2 V:视图(View)

  • 对应模板渲染

1.3 VM:视图模型

  • 对应Vue实例对象
MVVM模型图

2. Object.defineProperty

2.1 ES6为对象添加属性

  • Object.defineProperty(originObject,attributeName,attributeValue)
let person = {
    name:"zhiyuan",
    gender:"male"
}
Object.defineProperty(person,'age',{
    value:18
});

注意:通过defineProperty直接添加的对象属性,是无法进行枚举的,即遍历对象无法取得该值,且我们无法修改或删除这个属性

如何直观的看到?

  • Object.keys(object)
    • 使用keys函数可以将指定对象的key值抽取为一个数组,我们会发现新加的属性没有出现在数组中

如何解决这个问题?

Object.defineProperty(person,'age',{
    value:18,
    enumerable:true, //将enumrable置为true,就可以对其进行枚举了
    //为了方便使用,我们将writable,configurable属性也置为true,这样就可以对该属性进行修改删除了	
    writable:true,
    configurable:true
});
  • 为属性增加getter和setter方法

    let age = 18;
    let user = {
        username:"zhiyuan",
        nickname:"絷缘",
        password:"123456",
        gender:"male"
    }
    Object.defineProperty(user,'age',{
       	get:function(){
            return age;
        },
        set:function(age){
            this.age = age;
        }
    });
    

2.2 数据代理

概念:指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。

Vue实例中的data就是实现了数据代理,data在Vue实例中实际存在的属性名称为_data

3. 事件处理

3.1 methods

const vm = new Vue({
   el:"#container",
   data:{
       user:{
           username:"zhiyuan",
    		nickname:"絷缘",
    		password:"123456",
    		gender:"male",
           	age:18
       }
   },
   methods:{
       methodName(event){
           //methodContent
    		console.log(this===vm)	//true 此处this为vm实例对象
       }
   } 
}};

3.2 事件绑定

  • 格式:v-on:[event]
<div id="container">
	<button v-on:click="showInfo($event)">按钮</button>
</div>
<script type="text/javascript">
	const vm = new Vue({
        el:"#container",
        data:{
            user:{
                username:"zhiyuan",
    			nickname:"絷缘",
    			password:"123456",
    			gender:"male",
           		age:18
            }
        },
        methods:{
            showInfo(event){
                alert("按钮被点击");
            }
        }
    });
</script>
  • 简写:@[event]
    • 例:v-on:click == @click

      <button @click="showMessage($event,传递参数)">点我提示信息</button>
      
      new Vue({
      	el:"#root",
      	data:{},
      	methods:{
      		showMessage(event,传递参数){
                  console.log(event.target.innerText,传递参数);
              }
      	}
      });
      

3.3 事件修饰符

  • 阻止默认行为:.prevent
  • 阻止事件冒泡:.stop
  • 事件只触发一次:.once
  • 使用事件捕获模式:.capture
  • 只有event.target是当前操作的元素时才出发事件:.self
  • 立即执行事件默认行为,无需等待事件回调执行完毕:.passive
    • @wheel:鼠标滚轮滚动事件
    • @scroll:滚动条滚动事件

3.4 键盘事件

  • Vue中提供的常用按键别名

    • enter :回车键
    • delete :退格键和删除键
    • esc :ESC键
    • space :空格键
    • tab :Tab键
    • up :方向键上
    • down :方向键下
    • left :方向键左
    • right :方向键右

    Vue未提供别名的按键,可以使用原始key值绑定,但要注意转换为短横线命名方式,如:caps-lock

    注意:tab键在@keyup事件中无效,因为tab键默认会切换焦点区域,必须配合@keydown才能正常使用

  • 系统修饰键

    • ctrl
    • alt
    • shift
    • win:windows徽标键

    注意:系统修饰键配合@keydown才可以正常触发事件,如果是@keyup需要配合其他按键才能正常触发

  • Vue中自定义按键别名

    Vue.config.keyCodes.huiche = 13;
    

4. 计算属性

4.1 基本概念

所谓的计算属性就是用几个data中已有的属性进行处理计算后得到一个全新的属性

const vm = new Vue({
    el:"#container",
    data:{
        firstName:"絷缘",
        lastName:"解"
    },
    methods:{},
    computed:{
        fullName:{
            get(){
                return this.firstName + "-" + this.lastName;
            },
            set(value){
                cosnt array = value.split("-");
                this.firstName = array[0];
                this.lastName = array[1];
            }
        }
    }
})

4.2 getter和setter调用时机

  • get调用时机
    • 初次读取计算属性时
    • 计算属性所依赖的其他属性发生变化时
  • set调用时机
    • 当计算属性被修改时

4.3 简写形式

  • 简写

    • 应用场景:计算属性只读不写,默认当做getter使用
    const vm = new Vue({
        el:"#container",
        data:{
            firstName:"絷缘",
            lastName:"解"
        },
        methods:{},
        computed:{
            fullName(){
            	return this.firstName + "-" + this.lastName;
            }
        }
    })
    

5. 监视属性

5.1 在watch属性中书写监视内容

  • 监视属性不仅可以监视data中声明的属性,还可以监视计算属性
const vm = new Vue({
    el:"#container",
    data:{
        isLogin:true,
    },
    methods:{},
    computed:{},
    watch:{
        'isLogin':{
            //初始化时让handler执行一次
            immediate:true,
            //当被监视属性改变时会调用handler方法
            handler(newValue,oldValue){
                console.log(isLogin==true?"已登录":"未登录");
            }
        }
    }
});

5.2 vm实例调用$watch方法书写监视内容

vm.$watch('isLogin',{
    //初始化时让handler执行一次
    immediate:true,
    //当被监视属性改变时会调用handler方法
    handler(newValue,oldValue){
        console.log(isLogin==true?"已登录":"未登录");
    }
})

5.3 深度监视

  • Vue的watch默认不监视对象内部值的改变,要想让watch可以监视到对象内部属性的变化,需要配置deep:true

watch中默认监视外层对象,无法监视多级结构的内容改变,当外层对象改变才会执行handler,内部成员的改变不会触发handler,如果我们想要监视内部成员的改变,需要在watch对应监视属性中添加 deep:true

const vm = new Vue({
    el:"#container",
    data:{
        user:{
            username:"zhiyuan",
            gender:"male"
        }
    }
    watch:{
    	'user':{
    		deep:true,
    		handler(){
    			console.log("user被改变");
			}
		}
	}
})

此时我们改变user中的属性值,handler也会被触发执行。

5.4 简写形式

watch使用简写方式的代价就是无法进行除了handler方法外的配置,比如深度监视,比如初始化执行

const vm = new Vue({
    el:"#container",
    data:{
        isLogin:true
    }
    watch:{
    	isLogin(newValue,oldValue){
    		console.log('isLogin被修改',newValue,oldValue);
		}
	}
})

6. 计算属性与监视属性的区别

6.1 区别

  1. computed可以完成的功能,watch都可以完成
  2. watch可以完成的功能,computed不一定能够完成,例如watch可以进行异步操作

6.2 注意事项

注意:

  1. 被Vue所管理的函数应该写成普通函数,这样this的指向才是vue实例对象
  2. 所有不被Vue管理的函数(定时器回调函数,ajax回调函数,Promise的回调函数等),最好写成箭头函数,这样this的指向才是vue实例对象,否则它们将指向约定的this为window对象

7. 绑定样式

7.1 绑定class

步骤
  • 先定义好对应选择器的样式
  • 将选择器的名称声明到data数据中
  • 将data数据中的选择器名称与class绑定

7.2 绑定style

①. 写法一
<div id="container" :style="{fontSize:fontSize+'px'}">
    内容
</div>
<script type="text/javascript">
    const vm = new Vue({
        el:"#container",
        data:{
            fontSize:40
        }
    });
</script>
②. 写法二
<div id="container" :style="styleObj">
    内容
</div>
<script type="text/javascript">
    const vm = new Vue({
        el:"#container",
        data:{
            styleObj:{
                fontSize:'40px',
                color:'#666'
            }
        }
    });
</script>
③. 写法三
<div id="container" :style="[styleObj1,styleObj2]">
    内容
</div>
<script type="text/javascript">
	const vm = new Vue({
        el:"#container",
        data:{
            styleObj1:{
                fontSize:'40px'
            },
            styleObj2:{
                color:'#666'
            }
        }
    });
</script>

8. 条件渲染

8.1 v-if

<div class="container">
    <h2 v-if="isLogin === false">登录注册</h2>
    <h2 v-else>已登录</h2>
</div>
<script type="text/javascript">
    const vm = new Vue({
        el:".container",
        data:{
            isLogin:false
        }
    });
</script>

8.2 v-show

<div class="container">
    <h2 v-show="isLogin === false">登录注册</h2>
    <h2 v-show="isLogin">已登录</h2>
</div>
<script type="text/javascript">
    const vm = new Vue({
        el:".container",
        data:{
            isLogin:false
        }
    });
</script>

8.3 v-if与v-show的区别

  • 区别
    1. v-if 适用于切换频率比较低的场景,因为它是通过DOM渲染节点来完成的显示与隐藏,当不满足条件时,页面元素会被移除,满足条件时又会被创建,所以当大量的元素需要频繁切换的时候,v-if不是很好的解决方案
    2. v-show 适用于切换频率较高的场景,因为它是通过元素的css样式 display:none 属性来实现元素的显示与隐藏的,当满足条件时, display:none 被移除,元素显示,不满足条件时, display:none 被添加,元素隐藏

8.4 条件相同时条件渲染书写方式

  • <div class="container">
        <h2 v-if="isLogin === false">登录</h2>
        <h2 v-if="isLogin === false">注册</h2>
    </div>
    <!--v-if条件相同重复书写,进一步优化-->
    <div class="container">
        <div v-if="isLogin === false">
            <h2>登录</h2>
        	<h2>注册</h2>
        </div>   
    </div>
    

    上面的优化虽然实现了效果也避免了条件渲染重复,但是加入了新的没有必要的页面元素,一定程度影响了布局,不是非常合适,那么有没有一种办法解决这个问题呢?

    Vue为我们提供了一个名为 <template> 的标签,这个标签可以与条件渲染 v-if 结合使用而且不影响页面结构,注意只能与 v-if 结合使用

9. 列表渲染

9.1 基本列表

<div class="container">
    <!-- 遍历数组 -->
    <ul class="list">
        <li class="list-item" v-for="(user,index) in userList" :key="user.uid">
            {{ user.uid + "-" + user.username + "-" + user.age}}
        </li>
    </ul>
    <!-- 遍历对象 -->
    <ul class="list">
        <li class="list-item" v-for="(value,key) of admin" :key="key">
            {{ key + "-" + value }}
        </li>
    </ul>
    <!-- 遍历字符串 -->
    <ul class="list">
        <li class="list-item" v-for="(char,index) of username" :key="index">
            {{ index + "-" + char }}
        </li>
    </ul>
    <!-- 遍历指定次数 -->
    <ul class="list">
        <li class="list-item" v-for="(number,index) of 5">
            {{ index + "-" + number }}
        </li>
    </ul>
</div>
<script type="text/javascript">
    const vm = new Vue({
        el:'.container',
        data:{
            userList:[
                {uid:'001',username:'zhiyuan001',age:18},
                {uid:'002',username:'zhiyuan002',age:20},
                {uid:'003',username:'zhiyuan003',age:23},
            ],
            admin:{
                username:"zhiyuan",
                role:"admin",
                nickname:"絷缘",
                email:"1234567890@gmail.com"
            },
            username:"zhiyuan"
        }
    });
</script>

9.2 key的作用

之前代码中,我们使用index作为key,其实这是不正确的,虽然当前我们确实没有发现问题,但是当我们对原有数据进行打乱顺序的操作后,如果继续以index作为key,那么会导致vue对DOM元素的复用错乱,进而导致页面数据错乱的结果。

比如:当我们往数组的头部追加一项内容时,列表渲染会按索引与之前的DOM进行比较,查看是否可复用,但是我们现在的DOM元素的顺序已经发生了改变,在对其进行比较复用,就会导致元素和数据的错乱。

那么我们不写怎么样?

Vue会默认将当前数据的索引值作为当前DOM绑定的key值,所以造成的结果是一样的。

正确的key值绑定

key值一定是可以唯一标识当前元素的信息,例如数据的ID值,这样无论我们变换顺序或者做其它操作,都不会对最终列表渲染的结果产生影响。

key的作用

key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据新的数据生成新的虚拟DOM,随后与之前的虚拟DOM进行差异比较,比较规则如下:

  1. 旧的虚拟DOM中找到了与新的虚拟DOM相同的key
    • 若是虚拟DOM的内容没有发生改变,直接使用旧的虚拟DOM所生成的旧的真实DOM元素
    • 若是虚拟DOM的内容已经发生改变,则由新的虚拟DOM生成新的真实DOM元素
  2. 旧的虚拟DOM中未找到与新的虚拟DOM相同的key
    • 根据新的虚拟DOM创建新的真实DOM元素

10. 列表过滤

10.1 方式一:监视属性

<div class="container">
    <input type="text" v-model="keyWord" placeholder="搜索...">
    <ul class="list">
        <li class="list-item" v-for="(user,index) in tempList" :key="user.uid">
            {{ user.uid + "-" + user.username + "-" + user.age}}
        </li>
    </ul>
</div>
<script type="text/javascript">
    const vm = new Vue({
        el:".container",
        data:{
            userList:[
                {uid:'001',username:'胡歌',age:18,gender:'male'},
                {uid:'002',username:'霍建华',age:20,gender:'male'},
                {uid:'003',username:'焦恩俊',age:23,gender:'male'},
                {uid:'004',username:'胡一菲',age:20,gender:'female'},					
                {uid:'005',username:'胡图图',age:6,gender:'male'},					
                {uid:'006',username:'刘诗诗',age:21,gender:'female'},					
                {uid:'007',username:'刘亦菲',age:22,gender:'female'},				
                {uid:'008',username:'刘德华',age:22,gender:'male'}			
            ],
            keyWord:'',
            tempList:[]
        },
        watch:{
            'keyWord':{
                immediate:true,
                handler(newValue,OldValue){
                    this.tempList = this.userList.filter((user)=>{
                        return user.username.indexOf(newVlaue) != -1;
                    })
                }
            }
        }

    });
</script>

10.2 方式二:计算属性

<div class="container">
    <input type="text" v-model="keyWord" placeholder="搜索...">
    <ul class="list">
        <li class="list-item" v-for="(user,index) in tempList" :key="user.uid">
            {{ user.uid + "-" + user.username + "-" + user.age}}
        </li>
    </ul>
</div>
<script type="text/javascript">
    const vm = new Vue({
        el:".container",
        data:{
            userList:[
                {uid:'001',username:'胡歌',age:18,gender:'male'},
                {uid:'002',username:'霍建华',age:20,gender:'male'},
                {uid:'003',username:'焦恩俊',age:23,gender:'male'},
                {uid:'004',username:'胡一菲',age:20,gender:'female'},
                {uid:'005',username:'胡图图',age:6,gender:'male'},	
                {uid:'006',username:'刘诗诗',age:21,gender:'female'},
                {uid:'007',username:'刘亦菲',age:22,gender:'female'},				
                {uid:'008',username:'刘德华',age:22,gender:'male'}			
            ],
            keyWord:''
        },
        computed:{
            tempList:{
                get(){
                    return this.userList.filter((user)=>{
                        return user.username.indexOf(this.keyWord) != -1;
                    });
                }
            }
        }
    });
</script>

11. 列表排序

<div class="container">
    <input type="text" v-model="keyWord" placeholder="搜索...">
    <input type="button" value="年龄降序" @click="sortType = 1">
    <input type="button" value="年龄升序" @click="sortType = 2">
    <input type="button" value="原顺序" @click="sortType = 0">
    <ul class="list">
        <li class="list-item" v-for="(user,index) in tempList" :key="user.uid">
            {{ user.uid + "-" + user.username + "-" + user.age}}
        </li>
    </ul>
</div>
<script type="text/javascript">
    const vm = new Vue({
        el:".container",
        data:{
            userList:[
                {uid:'001',username:'胡歌',age:18,gender:'male'},
                {uid:'002',username:'霍建华',age:20,gender:'male'},
                {uid:'003',username:'焦恩俊',age:23,gender:'male'},
                {uid:'004',username:'胡一菲',age:20,gender:'female'},					
                {uid:'005',username:'胡图图',age:6,gender:'male'},					
                {uid:'006',username:'刘诗诗',age:21,gender:'female'},					
                {uid:'007',username:'刘亦菲',age:22,gender:'female'},				
                {uid:'008',username:'刘德华',age:22,gender:'male'}			
            ],
            keyWord:'',
            //sortType为0时正常排序,为1时按年龄降序,为2时按年龄升序
            sortType:0
        },
        computed:{
            tempList:{
                get(){
                    const arr = this.userList.filter((user)=>{
                        return user.username.indexOf(this.keyWord)!=-1;
                    });
                    if(this.sortType){
                        arr.sort((u1,u2)=>{
                            return this.sortType===1 ? u2.age-u1.age :u1.age-u2.age;
                        });
                    }
                    return arr;
                }
            }
        }
    });
</script>

12. Vue监测数据的原理

  1. Vue会监视data中所有层次的数据
  2. 如何监测对象中的数据?
    • 通过setter实现监视,且要在new Vue时就传入要监测的数据
      • 对象中后追加的属性,Vue默认不做响应式处理
      • 如需给后添加的属性做响应式,请使用如下API
        • Vue.set(target,propertyName/index,value)
        • this.$set(target,propertyName/index,value)
  3. 如何监测数组中的数据?
    • 通过包裹数组更新元素的方法实现,本质上就是做了两件事
      • 调用原生JS对应的数组方法进行更新
      • 重新解析模板,更新页面
  4. 在Vue修改数组中的某个元素一定要用如下方法(即Vue重新包装过的方法):
    • push pop shift unshift splice sort reverse
    • Vue.set() this.$set()

13. 收集表单数据

v-model的三个修饰符

  • lazy :失去焦点再收集数据
  • number :输入字符串转为有效的数字
  • trim:输入首尾空格过滤

收集表单数据注意事项:

  1. v-model搜集的是value值,用户的输入就是value值
  2. v-model搜集的是value值,而且需要给标签设置value值
  3. 1. 如果没有设置value值,默认搜集checked的值,即true or false 2. 设置了value值 1. data中搜集此项的属性默认值为字符串,那么vue还是会搜集checked的值 2. data中搜集此项的属性默认值为数组,那么vue搜集的就是value组成的数组

14. 过滤器

过滤器

  • 定义
    • 对要展示的数据进行特定格式化后再显示,适用场景,后端商品价格数据,时间戳格式修改为前端展示格式
  • 语法
    • 注册过滤器:Vue.filter(name,callback)new Vue({filters:{...}})
    • 使用过滤器:{{ 参数 | 过滤器名 }}v-bind:属性名=" 参数 | 过滤器名"
  • 备注
    • 过滤器也可以接受额外参数,多个过滤器可以串联使用,后一个过滤器的参数为前一个过滤器的返回结果
    • 过滤器并没有改变原本的数据,只是产生了格式化后的新数据

14.1 局部过滤器

<div id="container">
	<h3>{{formatTime}}</h3>
    <h3>{{getFormatTime()}}</h3>
    <h3>{{time|timeFormatFilter('YYYY-MM-DD')}}</h3>
</div>
<script src="./js/dayjs.min.js"></script>
<script	type="text/javascript">
	const vm = new Vue({
        el:"#container",
        data:{
            time:1641475263843
        },
        computed:{
            formatTime(){
                return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss');
            }
        },
        methods:{
            getFormatTime(){
                return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss');
            }
        },
        filters:{
            timeFormatFilter(value,formatStr='YYYY-MM-DD HH:mm:ss'){
                return dayjs(value).format(formatStr);
            }
        }
    });
</script>

14.2 全局过滤器

<script type="text/javascript">
    Vue.filter('filterName',function(value){
       return value.slice(0,3);
    });
</script>

过滤器只能与插值表达式或者v-bind使用,无法与v-model结合使用

15. 其他内部指令

15.1 v-text

  • 向其所在的节点中渲染文本内容,不过它会替换掉节点中已有的内容
<div id="container">
	<div v-text="text"></div>
</div>
<script	type="text/javascript">
	const vm = new Vue({
         el:"#container",
        data:{
            text:"文本内容"
        }
    });
</script>

15.2 v-html

<div id="container">
	<div v-html="html"></div>
</div>
<script	type="text/javascript">
	const vm = new Vue({
         el:"#container",
        data:{
            html:"<h1>html内容</h1>"
        }
    });
</script>

15.3 v-cloak

  • 当容器被Vue实例接管的时候,自动删除本属性

使用场景:在Vue实例未接管之前,隐藏没有数据的DOM元素

[v-cloak] {
  	display: none;
}

15.4 v-once

v-once指令在DOM节点初次动态渲染后,就变为静态内容了,之后无论绑定的数据发生何种变化,都不会引起当前内容的更新,用于优化性能

15.5 v-pre

v-pre指令会跳过当前节点的编译过程,如果没有使用指令语法、插值表达式的节点,添加v-pre,可以加快编译。使用过的节点则会使所使用的语法失效。

16. 自定义指令

16.1 函数式写法

<div id="container">
     <span v-text="number"></span>
     <span v-big="number"></span>
</div>
<script type="text/javascript">
	new Vue({
        el:"#container",
        data:{
            number:1
        },
        directives:{
            big(element,bindObject){
                element.innerText = bindObject.value * 10;
            }
        }
    });
</script>

自定义指令函数何时被调用?

  1. 指令与元素成功绑定时,调用一次
  2. 指令所在的模板被重新解析时,调用一次

16.2 对象式写法

<div id="container">
     <input type="text" v-fbind:value="number"/>
</div>
<script type="text/javascript">
    new Vue({
        el:"#container",
        data:{
            number:1
        },
        directives:{
            fbind:{
                //指令与元素成功绑定时被调用
                bind(element,bindObject){
                    element.value = bindObject.value;
                },
                //指令所在元素被插入页面时
                insert(element,bindObject){
                    element.focus();
                },
                //指令所在的模板被重新解析时
                update(element,bindObject){
                    elelment.value = bindObject.value;
                }                
            }
        }
    });
</script>   

16.3 常见问题

  1. 自定义指令命名时不要使用驼峰命名法,要转换为横线命名法,且在声明该指令函数时将函数名用引号包裹,避免Vue解析出错
  2. 自定义指令回调函数中的this是window对象,因为是由我们直接操作真实DOM的,Vue不会再管理this
  3. 一个Vue实例中的自定义指令只能被当前实例索使用,若想被其他vue实例使用,必须声明为全局指令

16.4 全局自定义指令

<script type="text/javascript">
    Vue.directive('name',{
       bind(element,bindObject){
           ......
       },
       inserted(element,bindObject){
           ......
       },
       update(element,bindObject){
           ......
       } 
    });
</script>

三、生命周期

Vue 实例生命周期

所谓生命周期函数就是Vue在一些关键的节点时刻为我们调用的特殊名称的回调函数

1. beforeCreate

刚刚完成初始化生命周期、事件,数据代理未开始,此时无法通过实例访问到data中的数据,methods中的方法

2. created

刚刚完成初始化数据监视、数据代理,此时可以通过实例访问到data中的数据,methods中的方法

3. beforeMount

刚刚完成模板解析,在内存中生成虚拟DOM,还未经Vue编译为真实DOM放入到页面,此时对DOM元素的所有操作最终都无效

4. mounted

Vue将虚拟DOM编译为真实DOM元素放入页面后,调用mounted函数

5. beforeUpdate

刚刚完成数据的修改,但是页面还没有改变,此时会调用beforeUpdate函数

6. updated

数据被修改,页面也得到了更新,此时调用updated函数

7. beforeDestory

实例中所有的属性(data、methods等)都处于可用状态,马上要执行销毁过程,调用beforeDestory函数,一般在此时:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作

注意:虽然此时所有的属性都可用,但对属性的更新不会再触发页面更新

8. destoryed

实例已经被销毁,实例的监听器,与其关联的子组件以及自定义事件全部被销毁

注意:

  1. 销毁后借助Vue开发工具看不到任何信息
  2. 销毁后所有自定义的事件失效,但是原生DOM事件仍然有效
  3. 一般不会在beforeDestory中操作数据,因为即使操作数据,也不会再触发更新了

四、组件化编程

  • 组件:实现应用中局部功能代码和资源的集合

组件中的data必须为函数形式,避免引用数据相互影响导致页面混乱

组件名注意事项:

  1. 一个单词组成
    • 首字母小写,即全小写
    • 首字母大写
  2. 多个单词组成
    • 短横线命名
    • 所有单词首字母大写(需要Vue脚手架支持)
  3. 注册组件时,尽量回避HTML原有的标签名,否则可能出现意想不到的错误,可以使用name属性在创建组件时指定组件名称

在页面中使用组件的方式:

  • <componentName></componentName>
  • <componentName/> (需要Vue脚手架支持)

1. 非单文件组件

1.1 创建组件

<script type="text/javascript">
	const headerNavigation = Vue.extend({
        template:`<h1>{{title}}</h1>`,
        data(){
        	return {
                title:"标题"
            }
    	},
        methods:{}
    });
</script>

1.2 注册组件

局部注册
<script type="text/javascript">
	const vm = new Vue({
        el:"#container",
        components:{
            'header-navigation':headerNavigation
        }
    });
</script>
全局注册
<script type="text/javascript">
	Vue.component('header-navigation',headerNavigation);
</script>

1.3 使用组件

<body>
	<div id="container">
        <header-navigation></header-navigation>
    </div>	
</body>

2. VueComponent构造函数

组件本质是一个名为 VueComponent 的构造函数,是由 Vue.extend 生成的

每次调用 Vue.extend 返回的都是一个全新的 VueComponent

在创建组件的过程中,thisVueComponent 实例对象

image-20220107211558018

VueComponent.propertype.__proto__ === Vue.propertype

3. 单文件组件

3.1 示例代码

<template>
<!--组件结构-->
  <div class="school">
      <h1>SchoolVueComponent</h1>
  </div>
</template>
<script>
//组件交互相关代码
export default {
    // eslint-disable-next-line vue/multi-word-component-names
    name: 'School',
    data(){
      return {
          schoolName: 'IMOOC',
          address: 'beijing'
      }
    },
    methods:{},
    computed:{},
    watch:{},
    mixins:{}
}
</script>
<style>
//组件样式
*{
    padding:0;
    margin:0;
}
</style>

五、VueCLI

1. 准备工作

1.1 安装NodeJS

下载地址:https://nodejs.org/dist/v16.13.2/node-v16.13.2-x64.msi

1.2 安装NPM

换源

  1. 方式一
    • npm install -g nrm
    • nrm ls
    • nrm use taobao
  2. 方式二
    • npm config set registry https://registry.npm.taobao.org
    • npm config get registry
  3. 方式三:临时指定下载源
    • npm --registry https://registry.npm.taobao.org install [name]
  4. 方式三
    • npm install -g cnpm --registry=https://registry.npm.taobao.org
    • cnpm install [name]

1.3 设置自定义缓存目录和模块下载安装目录

npm config set prefix E:\Development\NodeJS\npm_prefix
npm config set cache E:\Development\NodeJS\npm_cache

1.4 安装VueCLI

npm install -g @vue/cli

1.5 创建Vue项目

vue create [option] projectName

1.6 运行vue项目

vue serve

2. render函数

2.1 应用场景

当我们因为一些需求引入的并非是完整版的 vue 时,没有模板解析器,无法识别 template 项,导致页面无法展示,使用render函数可以将模板内容利用 createElement 函数创建并返回到页面中

import Vue from 'vue'
Vue.config.productionTip = false;
new Vue({
    el:'#app',
  	render(createElement){
    	return createElement('h1','标题内容');
  	}
});

render函数没有用到this,所以可以简写为箭头函数,又因为函数中只有一条返回语句,所以可以直接简写为如下格式:

import Vue from 'vue'
Vue.config.productionTip = false;
new Vue({
      el:'#app',
      render: h=>h('h1','标题内容');
});

2.2 总结

  • vue.jsvue.runtime.xxx.js的区别
    • vue.js 是完整版的 Vue,包含核心功能和模板解析器
    • vue.runtime.xxx.js 是运行版的 Vue,只包含核心功能没有模板解析器
  • vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置,需要使用 render 函数接收到的createElement 函数去指定模板内容

3. 覆写脚手架配置

3.1 查看脚手架配置

vue inspect > output.js

3.2 覆写配置 vue.config.js

module.exports = {
  pages:{
    index:{
    	//入口
      entry:'src/yourJs.js'
    }
  },
  //关闭语法检查
  lintOnSave:false
}
npm run serve

3.3 具体配置项

详见:https://cli.vuejs.org/zh/

4. ref属性

  • 被用来给元素或子组件注册引用信息(id的替代者)

  • 当我们为对应元素设置ref后,可以直接通过this调用$refs获取到对应名称的元素,如果为html元素则返回真实DOM对象,如果为组件元素则返回组件实例对象

<template>
  <img alt="Vue logo" src="./assets/logo.png" ref="img">
  <HelloWorld msg="Welcome to Your Vue.js App" ref="hw"/>
  <School ref="school"/>
  <button @click="showElement" ref="btn">点我输出元素对象</button>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'
import School from './components/School.vue';
export default {
  name: 'App',
  components: {
    HelloWorld,School
  },
  methods:{
      showElement(){
          console.log(this.$refs.img); //真实DOM元素
          console.log(this.$refs.btn); //真实DOM元素
          console.log(this.$refs.school); //组件实例对象
          console.log(this.$refs.hw); //组件实例对象
      }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

5. props配置

props属性:用于接收组件调用方使用组件时传入的数据

5.1 props简单接收

<template>
<!--组件结构-->
  <div class="school">
      <h1>{{ msg }}</h1>
      <h2>学校名称:{{schoolName}}</h2>
      <h2>学校地址:{{schoolAddress}}</h2>
      <h2>学校面积:{{schoolArea*100}}</h2>
  </div>
</template>
<script>
//组件交互相关代码
export default {
    // eslint-disable-next-line vue/multi-word-component-names
    name: 'School',
    data(){
      return {
          msg:"SchoolComponentMsg"
      }
    },
    //简单接收
    props:['schoolName','schoolAddress','schoolArea']
}
</script>
<style>
//组件样式
*{
    padding:0;
    margin:0;
}
</style>

5.2 props接收时限定传入的数据类型

<template>
<!--组件结构-->
  <div class="school">
      <h1>{{ msg }}</h1>
      <h2>学校名称:{{schoolName}}</h2>
      <h2>学校地址:{{schoolAddress}}</h2>
      <h2>学校面积:{{schoolArea*100}}</h2>
  </div>
</template>
<script>
//组件交互相关代码
export default {
    // eslint-disable-next-line vue/multi-word-component-names
    name: 'School',
    data(){
      return {
          msg:"SchoolComponentMsg"
      }
    },
    //接收的同时对数据类型进行限制
    props:{
        schoolName:String,
        schoolAddress:String,
        schoolArea:Number
    }
}
</script>
<style>
//组件样式
*{
    padding:0;
    margin:0;
}
</style>

5.3 props接收时限定传入数据的各项属性

<template>
<!--组件结构-->
  <div class="school">
      <h1>{{ msg }}</h1>
      <h2>学校名称:{{schoolName}}</h2>
      <h2>学校地址:{{schoolAddress}}</h2>
      <h2>学校面积:{{schoolArea*100}}</h2>
  </div>
</template>
<script>
//组件交互相关代码
export default {
    // eslint-disable-next-line vue/multi-word-component-names
    name: 'School',
    data(){
      return {
          msg:"SchoolComponentMsg"
      }
    },
    props:{
        schoolName:{
            type: String, //数据类型
            required: true //必须传入
            //default:"慕课网" 默认值
        },
        schoolAddress:{
            type:String, //数据类型
            required: true //必须传入
            //default: "上海" 默认值
        },
        schoolArea:{
            type: Number, //数据类型
            required: true //必须传入
            //default: 200 默认值
        }
    }
}
</script>
<style>
//组件样式
*{
    padding:0;
    margin:0;
}
</style>

5.4 修改传入数据的解决方案

Vue 默认不推荐我们直接修改传入的数据,虽然我们可以修改成功,但是Vue会报错,因为这个过程可能导致Vue 产生错误,但是我们在业务开发中仍然有这样的需求,解决方案就是我们通过在组件定义对应的承接传入数据的data属性,并让页面绑定我们组件内部的data属性,日后修改时只需要修改我们组件内部拥有传入数据的data属性即可重新完成模板解析,实现我们想要的效果

6. mixin混入

mixin混入:一段配置让多个组件共用时,可以使用混入完成

6.1 书写需要混入的内容

export const mixin = {
    data(){
        return {
            username:"zhiyuan",
            email:"zhiyuanworkemail@163.com",
            index:"https://www.zync.top"
        }
    }
}

6.2 引入需要混入的内容

6.2.1 局部引入
<template>
<!--组件结构-->
  <div class="school">
      <h1>{{ msg }}</h1>
      <h2>学校名称:{{schoolName}}</h2>
      <h2>学校地址:{{schoolAddress}}</h2>
      <h2>学校面积:{{schoolArea*100}}</h2>
  </div>
</template>
<script>
import {mixin} from "@/mixin";
//组件交互相关代码
export default {
    name: 'School',
    data(){
      return {
          msg:"SchoolComponentMsg"
      }
    },
    mixins:[mixin]
}
</script>
<style>
//组件样式
*{
    padding:0;
    margin:0;
}
</style>
6.2.2 全局引入

在main.js中作如下修改

import Vue from 'vue'
import App from './App.vue'
import {mixin} from "@/mixin";
Vue.mixin(mixin);
new Vue({
    el:"#app",
    render: h=>h(App)
})

6.3 注意事项

在混入进行的时候,如果组件内部已经有对应data中同名的属性,则会以组件自己的为准

7. 插件

插件本质上就是一个对象,但是Vue规定这个对象中必须存在一个install方法,用于增强Vue,我们可以在其中定义全局的过滤器,定义自己的指令,定义全局的混入等等

7.1 示例

export default{
    install(){
        console.info('自定义插件');
    }
}

7.2 引入插件

//引入自定义插件
import plugins from "@/plugins";
Vue.use(plugins);

7.3 进阶

export default{
    install(Vue){
        console.info('自定义插件',Vue);
        //全局过滤器
        Vue.filter('filterName',function (methodParam){
            return methodParam;
        });
        //自定义指令
        Vue.directive('fbind',{
            bind(element,binding){
                element.value = binding.value;
            },
            inserted(element,binding){
                element.focus();
            },
            updated(element,binding){
                element.value = binding.value;
            }
        });
        //定义混入
        Vue.mixin({
            data(){
                return {
                    username:"zhiyuan",
                    gender:"male"
                }
            }
        })
    }
}

8. scoped样式

当我们书写的vue文件最终被解析为浏览器能够识别的js、css、html时,其实我们所有的css会完成汇总,而汇总样式就会产生一个问题,那就是选择器名称重复时,某些样式可能被覆盖,覆盖的原则是由引入组件的顺序决定的,谁先被引入,谁的样式就会被覆盖,但是我们通常不希望产生这样的结果,所以vue为我们提供了一个解决方案,那就是使用scoped属性,来限定css的作用域或者说css有效的范围

<style scoped>
//组件样式
*{
    padding:0;
    margin:0;
 }
</style>
  • 实现原理

    • Vue在汇总页面样式时,会在对应组件的容器上添加一个名为 data-v-xxxx 的属性,然后通过属性选择器来限定指定样式作用的容器
  • 书写less

    • 安装less-loader

      npm i less-loader@10.2.0
      
    • 书写less

      <style lang="less" scoped>
      </style>
      

9. ToDoList案例

  1. 组件化的编码流程

    • 拆分静态组件:组件按照功能点拆分,命名不要与html元素冲突

    • 实现动态组件:考虑好数据的存放位置,确认数据是一个组件在用还是一些组件都在用

      • 一个组件在用:放在组件自身即可

      • 一些组件在用:放在它们共同的父组件上

    • 实现交互:从绑定事件开始

  2. props适用于

    • 父组件与子组件进行通信
    • 子组件与父组件进行通信(需要父组件传给子组件一个函数)
  3. 使用v-model是要切记:v-model绑定的值不能是props传过来的值,因为props是不建议修改的

  4. props传过来的如果是对象类型的值,修改对象中的属性时Vue不会报错,但是不推荐这么做,因为这只是Vue没有检测到,并不意味着Vue允许我们这么做

10. 浏览器本地存储

10.1 LocalStorage

//存储基本类型到localStorage中最终都会呈现为字符串类型
window.localStorage.setItem('key','value');
//存储对象需要将对象转为JSON
window.localStorage.setItem('key',JSON.stringify(object));
//读取localStorage中存储的对象
JSON.parse(JSON.parse(localStorage.getItem('key'));
//从localStorage中移除设置的信息
window.localStorage.removeItem('key');      
//清空localStorage
window.localStorage.clear();

10.2 SessionStorage

//存储基本类型到sessionStorage中最终都会呈现为字符串类型
window.sessionStorage.setItem('key','value');
//存储对象需要将对象转为JSON
window.sessionStorage.setItem('key',JSON.stringify(object));
//读取sessionStorage中存储的对象
JSON.parse(JSON.parse(sessionStorage.getItem('key'));
//从sessionStorage中移除设置的信息
window.sessionStorage.removeItem('key');      
//清空sessionStorage
window.sessionStorage.clear();

LocalStorage:浏览器关闭后依然存在

SessionStorage:一次会话结束后就不存在了,即关闭浏览器后就不存在了

11. 组件自定义事件

  1. 一种组件间通信的方式,适用于子组件与父组件通信

  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)

  3. 绑定自定义事件

    • 第一种方式,在父组件中 <Demo @事件名="test"/><Demo v-on:事件名="test">

    • 第二种方式,在父组件中

      <Demo ref="demo">
      ......
      mounted(){
          this.$refs.xxx.$on('事件名',this.test);
      }
      
    • 若想让自定义事件只能触发一次,可以使用 once 修饰符,或 $once 方法

  4. 触发自定义事件 this.$emit('事件名',数据)

  5. 解绑自定义事件 this.$off('事件名')

  6. 组件上绑定原生DOM事件,需要使用 native 修饰符

  7. 注意:通过 this.$refs.xxx.$on('事件名',回调) 绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题

12. 全局事件总线

任意组件间通信

new Vue({
    el:"#app",
    render:h => h(App),
    //vm创建之前
    beforeCreate(){
        Vue.prototype.$bus = this; //安装全局事件总线
    } 
});
  1. 一种组件间的通信方式,适用于任意组件间通信

  2. 安装全局事件总线

    new Vue({
       el:"#app",
       render:h => h(App),
       beforeCreate(){
           Vue.prototype.$bus = this; //安装全局事件总线,$bus就是当前应用的vm对象
       } 
    });
    
  3. 使用事件总线

    1. 接收数据:A组件想接收数据,则在A组件汇总给$bus绑定自定义事件,事件的回调留在A组件自身

      methods(){
          demo(data){.......}
      }
      ......
      mounted(){
          this.$bus.$on('xxx',this.demo);
      }
      
    2. 提供数据:this.$bus.$emit('xxx',数据)

  4. 最好在 beforeDestory() 钩子函数中用 $off 去解绑当前组件所用到的事件

13. 消息订阅与发布 pubsub

仍然是一种支持任意组件通信的方式

  • 安装 pubsub-js
npm i pubsub-js
  • 引入pubsub
import pubsub from 'pubsub-js'
  • 发布消息
//组件挂载完毕
mounted(){
    //发布消息
    pubsub.publish('消息名','消息内容');
}
  • 订阅消息
//组件挂载完毕
mounted(){
    //订阅消息
    this.pubId = pubsub.subscribe('消息名',function('消息名','消息内容'){
        console.log('有人发布了消息,消息名为:' + 消息名 + '@消息内容为:' + 消息内容);
        ..... 
    });
}
//上方写法中存在错误,错在当前this并非是vc对象,老生常谈的,如果我们需要使用第三方js库时,vue不会帮我们在管理对应的函数、对象以及其他信息,如果想要被vue管理到,就需要把普通函数写成箭头函数,这样this就会去父级找到vm或者vc,正确写法有如下两种:

//第一种
methods:{
    demo('消息名','消息内容'){
        console.log('有人发布了消息,消息名为:' + 消息名 + '@消息内容为:' + 消息内容);
        ..... 
    }
}
//组件挂载完毕
mounted(){
    //订阅消息
    this.pubId = pubsub.subscribe('消息名',this.demo);
}

//第二种
//组件挂载完毕
mounted(){
    //订阅消息
    this.pubId = pubsub.subscribe('消息名',('消息名','消息内容') => {
        console.log('有人发布了消息,消息名为:' + 消息名 + '@消息内容为:' + 消息内容);
        ..... 
    });
}
  • 取消订阅
beforeDestory(){
    //根据订阅消息ID 取消订阅消息
    pubsub.unscribe(pubId);
}

14. $nextTick

this.$nextTick(回调函数) 指定的回调函数会在DOM节点更新之后执行

作用:在下一次DOM更新结束后执行其指定的回调

应用场景:当数据改变后,要基于 数据改变后 渲染的DOM进行某些操作时,可以使用此方法来指定

this.$nextTick(function(){
   this.$refs.inputTile.focus(); 
});

15. 过渡与动画

image-20230521124817364

15.1 动画

  • CSS3
@keyframes 动画名称 {
    from {
        transform: translateX(-100px);
    }
    to {
        transform: translateX(0px);
    }
}
.come { 
     /* animation:动画名称 持续时间 是否匀速 */
    animation: 动画名称 1s linear;    
}
.go{
    animation: 动画名称 1s linear reverse;
}
  • Vue

使用 <transition></transition> 标签包裹需要展示动画的内容

@keyframes 动画名称 {
    from {
        transform: translateX(-100px);
    }
    to {
        transform: translateX(0px);
    }
}
.v-enter-active { 
    animation: 动画名称 1s linear;    
}
.v-leave-active {
    animation: 动画名称 1s linear reverse;
}
<button @click="isShow = !isShow">显示/隐藏</button>
<transition>
	<h1 v-show="isShow">内容</h1>
</transition>
  • appear属性

    • 我们发现只有我们点击按钮才会触发动画效果,但是通常我们需要一个动画在页面展示时立即播放一次动画,name我们可以在 <transition></transition> 标签内添加 appear 属性
  • name属性

    • 我们可以给动画起名字,来让一个组件内可以有多个动画

      <transition name="动画名称"></transition>
      
    • 对应的css动画名称就需要改成如下格式

      .动画名称-enter-active {}
      .动画名称-leave-active {}
      
    • 其实我们默认不写,就是将动画名称默认为了 v

15.2 过渡

/* 进入的起点 */
.动画名称-enter {
    transform: translateX(-100%);
}
/* 进入的终点 */
.动画名称-enter-to {
    transform: translateX(0);
}
/* 离开的起点 */
.动画名称-leave {
    transform: translateX(0);
}
/* 离开的终点 */
.动画名称-leave-to {
    transform: translateX(-100%);
}
/* 进入的起点,离开的终点 */
.动画名称-enter,.动画名称-leave-to {
    transform: translateX(-100%);
}
/* 进入的终点,离开的起点 */
.动画名称-enter-to,.动画名称-leave{
    transform: translateX(0);
}
.动画名称-enter-active,.动画名称-leave-active{
    transition: 0.5s linear;
}

15.3 多个元素的过渡

  • <transition></transition>只能够加在一个单独的元素上,如果需要加在多个元素上需要使用<transition-group></transition-group>
  • 而且在使用 transition-group标签时,必须为其包裹的每一个元素设置一个key
<transition-group name="动画名称" appear>
	<h1 v-show="isShow" key="1"></h1>
    <h1 v-show="isShow" key="2"></h1>
</transition-group>

15.4 第三方动画

  • 安装 animate.css

    npm install animate.css
    
  • 引入 animate.css

    import 'animate.css'
    
  • 使用方式

    <transition-group name="animate__animated animate__bounce"
                      enter-active-class="animate__swing"
                      leave-active-class="animate__backOutUp"
                      appear>
    </transition-group>
    

16. 配置代理 axios

  • 安装 axios

    npm install axios
    
  • 引入axios

    import axios from 'axios'
    
  • 使用axios

    export default{
        name: 'App',
        methods:{
            getUserInfo(){
                axios.get('http://localhost:8080/cloud-drive/user/10000010').then(
                    response => {
                        console.log("请求成功",response.data);
                    },
                    error => {
                        console.log("请求失败",error.message);
                    }
                );
            }
        }
    }
    
  • 解决跨域问题

    • cors:后端放行跨域访问
    • json:利用了script标签的src属性在引入外部文件时不受同源策略影响的特性
    • 代理服务器
      • nginx
      • vue-cli

16.1 方式一

  • 修改 vue.config.js 文件
module.exports = {
    devServer:{
        proxy: 'http://localhost:8080'
    }
}

这个方法只能是请求地址没有的资源才走代理服务器,并不能解决我们的跨域问题

16.2 方式二

module.exports = {
    devServer:{
        proxy: {
            //匹配所有以/api开头的请求路径
        	'/cloud-drive':{
        		target:'http://localhost:8080',
                pathRewrite:{'^/cloud-drive':''} // key为书写的路径匹配正则,value为与匹配替换的路径
        		ws: true, // 开启webscoket支持
        		changeOrigin: true // 代理功能实现的核心属性,前端解决跨域问题
        	},
            //匹配所有以/foo开头的请求路径
        	'/admin':{
        		target: 'http://localhost:8085'
        	}
        }
    }
}

17. vue-resource

  • 安装 vue-resource

    npm install vue-resource
    
  • 引入 vue-resource

    //main.js 引入插件
    import vueResource from 'vue-resource'
    Vue.use(vueResource);
    

官方已经不再维护,不建议使用

18. 插槽

作用:让父组件可以向子组件指定位置插入html结构

应用场景:当一个组件被使用多次,但是组件中部分需求有所不同时,可以使用vue的插槽功能,让不同的需求都可以有对应的实现

18.1 默认插槽

<template>
    <h1>demo组件</h1>
	<slot>默认插槽</slot>
</template>
<demo>
    <h2>使用默认插槽</h2>
</demo>

18.2 具名插槽

<template>
	<h1>demo组件</h1>
    <slot name="center">我是具名插槽center</slot>
    <slot name="footer">我是具名插槽footer</slot>
</template>
<demo>
	<h2 slot="center">使用具名插槽center</h2>
    <h2 slot="footer">使用具名插槽footer</h2>
</demo>
<demo>
	<template slot="center">
    	<h2>使用具名插槽center</h2>
    </template>
    <template v-slot:footer>
    	<h2>使用具名插槽footer</h2>
    </template>
</demo>

18.3 作用域插槽

  • 数据在组件的自身,但是根据数据生成的结构需要组件的使用者来决定
<template>
	<h1>demo组件</h1>
    <slot :userList="userList"></slot>
</template>
<template>
	<demo>
        <template scope="userList">
            <ul>
                <li v-for="(user,index) in userList" :key="user.id">{{user.name}}</li>
            </ul>
        </template>
    </demo>
    <demo>
    	<template slot-scope="userList">
            <h2>{{userList[0].name}}</h2>
        </template>
    </demo>
</template>

19. vuex

作用:专门在Vue中实现集中式状态数据管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理,也是一种组件间通信方式,且适用于任意组件间通信

地址:https://github.com/vuejs/vuex

应用场景:

  1. 多个组件依赖于同一状态
  2. 来自不同的组件的行为需要变更为同一状态
image-20230521145745206
image-20230521150110566

19.1 vuex工作原理图

vuex

19.2 快速入门

  • 安装 vuex

在2022年2月7日,vue3 成为了默认版本,使用 npm install vue 如果不指定版本号,那么就会直接安装 vue3,并且使用 npm install vuex 不指定版本号,也会直接安装vuex4版本,vue3.0 对应 vuex4.0

npm install vuex@3
  • 引入 vuex

    import Vue from "vue"
    import Vuex from "vuex"
    Vue.use(Vuex);
    
  • 创建src/store目录,并新建index.js文件

    //引入Vue
    import Vue form 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    Vue.use(Vuex);
    //用于响应组件中的动作
    const actions = {
        
    }
    //用于操作数据state
    const mutations = {
        
    }
    //用于存储数据
    const state = {
        
    }
    //创建store
    const store = new Vuex.Store({
        actions:actions,
        mutations:mutations,
        state:state
    });
    //暴露store
    export default store;
    
  • 引入 store ,修改 main.js 文件如下

    import store from './store'
    

19.3 getter配置项

const getters = {
    ......
}
const store = new Vuex.Store({
	......
    getters:getters
});

19.4 mapState与mapGetters

  • 引入 mapState mapGetters

    import {mapState,mapGetters} from 'vuex'
    
  • 使用 mapState mapGetters

    computed:{
        //借助mapState生成计算属性,从state中读取数据
        ...mapState({sum:'sum',school:'school',subject:'subject'}),
        ...mapState(['sum','school','subject']) //简写形式
        //借助mapGetters生成计算属性,从getters中读取数据
        ...mapGetters({sum:'sum',school:'school',subject:'subject'})
        ...mapGetters(['sum','school','subject'])
    }
    

19.5 mapActions与mapMutations

  • 引入 mapMutations mapActions

    import {mapMutations,mapActions} from 'vuex'
    
  • 使用 mapMutations mapActions

    methods:{
        //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations中的方法
        ...mapMutations({increment:'SUM',decrement:'SUB'})
        ...mapMutations(['SUM','SUB']); //简写形式
        //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions中
        ...mapActions({increment:'sum',decrement:'sub'})
        ...mapActions(['sum','sub'])
    }
    

19.6 多组件共享数据

19.7 vuex的模块化编码

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const options1 = {
    namespaced:true, //开启命名空间
    action:{},
    mutation:{},
    state:{},
    getters:{}
}
const options2 = {
    namespaced:true, //开启命名空间
    action:{},
    mutation:{},
    state:{},
    getters:{}
}
export default new Vuex.Store({
   modules:{
       a:options1,
       b:options2
   } 
});
...mapState('a',['sum','school','subject']);
...mapState('b',['persionList'])
this.store.state.personAbout.list;
...mapMutations('a',{increment:'SUM',decrement:'SUB'}) 
this.$store.commit('a/SUM',2);
...mapActions('a',{increment:'sum',decrement:'sub'})
this.$store.dispatch('a/sum',2);
...mapGetters('a',['sum','school','subject'])
this.$store.getters['a/sum'];

20. 路由

SPA(Single Page Web Application):单页面应用

2022年2月7日以后,vue-router的默认版本变为了4,所以如果我们不需要最新版本,就要在安装时指定他的版本

  • 安装 vue-router

    npm install vue-router
    
  • 引入 vue-router

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    Vue.use(VueRouter);
    new Vue({
        el: '#app',
        render: h => h(App),
        router:''
    })
    
  • 创建src/router目录,并新建index.js文件

    import VueRouter from 'vue-router'
    import About from '../components/About'
    import Home from '../component/Home'
    //创建并暴露一个路由器
    export default new VueRouter({
        routes:[
            {
                path:'/about',
                component:About
            },
            {
                path:'/home',
                component:Home
            }
        ]
    });
    
  • 在页面中使用 <router-link> 标签的路由切换 代替 <a> 标签的页面跳转

    <router-link to="/about" active-class="active">About</router-link>
    <router-link to="/home" active-class="active">Home</router-link>
    
  • 在页面中使用 <router-view> 标签指定路由切换对应组件呈现的位置

    <router-view></router-view>
    

20.1 注意事项

  • 路由组件一般放在 pages 文件夹,普通组件一般放在 components
  • 通过切换,隐藏了的路由组件,默认是被销毁掉的,需要的时候才会再次挂载
  • 每个组件都有自己的 $route 属性,里面存储着自己的路由信息
  • 整个应用只有一个 router,可以通过组件的 $router 属性获取到

20.2 嵌套路由

export default new VueRouter({
   routes:[
       {
           path:'/about',
           component:About
       },
       {
           path:'/home',
           component:Home,
           children:[
               {
                   path:'news',
                   component:News
               },
               {
                   path:'message',
                   component:Message
               }
           ]
       }
   ] 
});

20.3 路由的query参数

  • 传递参数

    <!--to的字符串写法-->
    <router-link :to="/home/message/detail?id=666&title=test">跳转</router-link>
    <!--to的对象写法-->
    <router-link :to="{
                      path:'/home/message/detail',
                          query:{
                            id:666,
                      		title:'test'
                          }
                      }">跳转
    </router-link>            
    
  • 接收参数

    $route.query.id
    $route.query.title
    

20.4 命令路由

  • 作用:简化路由的跳转
export default new VueRouter({
   routes:[
       {
           path:'/about',
           component:About
       },
       {
           path:'/home',
           component:Home,
           children:[
               {
                   path:'news',
                   component:News
               },
               {
                   name:'message'
                   path:'message',
                   component:Message
               }
           ]
       }
   ] 
});
<router-link :to="{name:'message'}">跳转</router-link>
<router-link :to="{
                  	name:'message',
                  	query:{
                  		id:666,
                  		title:'test'
                  	}
                  }">跳转
</router-link>

20.4 路由的params参数

<router-link :to="{name:'message'}">跳转</router-link>
<router-link :to="{
                  	name:'message',
                  	params:{
                  		id:666,
                  		title:'test'
                  	}
                  }">跳转
</router-link>

当通过params传递参数时,只能用name,不能用path

20.5 路由的props配置

export default new VueRouter({
   routes:[
       {
           path:'/about',
           component:About
       },
       {
           path:'/home',
           component:Home,
           children:[
               {
                   path:'news',
                   component:News
               },
               {
                   name:'message'
                   path:'message',
                   component:Message,
                   children:[
                   		{
                   			name:'detail',
                   			path:'detail/:id/:title',
                   			component:Detail,
                   			//第一种写法,值为对象,缺点:数据写死,一般不用
                   			props:{
                   				id:'666',
                   				title:'test'
               				}
               				//第二种写法,值为boolean,true时会把该路由组件收到的所有params参数以props形式传给Detail组件
               				props:true
               				//第三种写法,值为函数
               				props(){
           						return {id:'666',title:'test'}
       						}
       						
               			}
                   ]
               }
           ]
       }
   ] 
});
props($route){
    return {
        id:$route.query.id,
        title:$route.query.title
    }
}

20.6 router-link的replace属性

  • 作用:控制路由跳转时操作浏览器历史记录的模式
  • 浏览器的历史记录由两种写入方式,分别为 pushreplace,push是追加历史记录,replace是替换当前记录,路由跳转默认为push
  • 我们只需要在 <router-link replace>标签中添加replace属性,就可以实现浏览器没有历史记录即无法后退的场景

20.7 编程式路由导航

  • 作用:不用借助 router-link 标签实现路由跳转,让路由跳转更加灵活

  • 代码

    this.$router.push({
        name:'detail',
        params:{
            id:xxx,
            title:xxx
        }
    })
    this.$router.replace({
        name:'detail',
        params:{
            id:xxx,
            title:xxx
        }
    })
    //前进
    this.$router.forward();
    //后退
    this.$router.back();
    //前进(>0)/后退(<0)
    this.$router.go(3);
    

20.8 缓存路由组件

  • 作用:让不展示的路由组件保持挂载,不被销毁

  • 代码

    <!--缓存单个路由组件-->
    <keep-alive include="News">
    	<router-view></router-view>
    </keep-alive>
    <!--缓存多个路由组件-->
    <keep-alive :include="['News','Message']">
    	<router-view></router-view>
    </keep-alive>
    

20.9 两个新的生命周期钩子

//激活
actived(){
    .......
},
//失活    
deactived(){
    .......
}   

20.10 路由守卫

  • 作用:对路由进行权限控制

  • 分类:全局守卫、独享守卫、组件内守卫

  • 全局前置守卫:初始化时执行,每次路由切换前执行

    const router = new VueRouter({options});
    router.beforeEach((to,from,next)=>{
        if(to.meta.isAuth){
            if(localStorage.getItem('role') === 'admin'){
                next();
            }else{
                alert('无权查看');
            }
        }else{
            next();
        }
    });
    
  • 全局后置守卫:初始化时执行,每次路由切换后执行

    const router = new VueRouter({options});
    router.afterEach((to,from)=>{
        if(to.meta.title){
            document.title = to.meta.title;
        }else{
            document.title = '主页';
        }
    });
    
  • 独享路由守卫

    export default new VueRouter({
       routes:[
           {
               path:'/about',
               component:About
           },
           {
               path:'/home',
               component:Home,
               children:[
                   {
                       name:'news',
                       path:'news',
                       component:News
                       meta:{title:'新闻'},
                   	   beforeEnter:(to,from,next)=>{
               				if(to.meta.isAuth){
                                if(localStorage.getItem('role') === 'admin'){
                                next();
                                }else{
                                    alert('无权查看');
                                }
                            }else{
                                next();
        					}
           				}
                   },
                   {
                       name:'message'
                       path:'message',
                       component:Message
                   }
               ]
           }
       ] 
    });
    
  • 组件内路由守卫

    //通过路由规则,进入该组件时被调用
    beforeRouteEnter(to,from.next){
        
    },
    //通过路由规则,离开该组件时被调用
    beforeRouteLeave(to,from,next){
        
    }    
    

20.11 history模式与hash模式

  • 对于一个url来说,在 # 后面的内容就是hash值
  • hash值不会包含在http请求中
  • 两种模式
    • hash模式
      • 地址中永远带着 # 号,不美观
      • 地址通过第三方分享,若第三方校验严格,地址会被标记为不合法
      • 兼容性好
    • history模式
      • 地址干净,美观
      • 兼容性相较hash模式较差
      • 应用部署上线时,需要后端人员支持,解决刷新页面服务端404的问题

六、Element-UI

地址:https://element.eleme.cn/#/zh-CN

七、Vue3

1. 使用 vue-cli 创建工程

2. 使用vite创建工程

3. 分析工程结构

3.1 分析入口文件main.js

//引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import {createApp} from 'vue'
import App from './App.vue'
//创建应用实例对象(类似于vue2的vm,但是比vm更加轻量)
const app = createApp(App);
app.mount("#app");

3.2 分析App.vue

<template>
	<!--vue3组件中的模板结构可以没有根标签-->
    <img alt="Vue logo" src="./assets/logo.png" ref="img">
    <HelloWorld msg="Welcome to Your Vue.js App" ref="hw"/>
    <School ref="school" school-name="黑马程序猿" school-address="北京" v-bind:school-area="10"/>
    <button @click="showElement" ref="btn">点我输出元素对象</button>
</template>

4. 安装开发者工具

5. 初识setup

Vue3中一个新的配置项,值为一个函数

组件中用到的数据data,方法methods等,均要配置到setup中

  • setup函数有两种返回值
    • 返回一个对象,则对象中的属性、方法。在模板中均可以直接使用
    • 若返回一个渲染函数,则可以自定义渲染内容
  • 注意事项
    • 尽量不要与vue2配置混用
      • vue2配置(data、methods、computed、watch...)中可以访问到setup中的属性、方法
      • 但是setup中不能访问vue2的配置(data、methods、computed、watch...)
      • 如果有重名,以setup优先
    • setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性
      • 后期可以返回一个Promise实例,但是需要Suspense和异步组件的配合

6. ref函数

  • 作用:定义一个响应式数据

  • 语法:const xxx = ref(initValue)

    • 创建一个包含响应式数据的引用对象
    • js中操作数据 xxx.value
    • 模板中读取数据不需要 .value,直接 {{xxx}}
  • 备注

    • 接收的数据可以是基本类型,也可以是对象类型
    • 基本类型的数据,响应式依然是靠 Object.defineProperty()getset 完成的
    • 对象类型的数据,内部求助了Vue3的一个新函数 reactive 函数
  • import {ref} from 'vue'
    

7. reactive函数

  • 作用:定义一个对象类型的响应式数据(基本类型别用它,用 ref 函数)
  • 语法:const 代理对象 = reactive(被代理对象) 接收一个对象(或数组),返回一个代理器对象(Proxy对象)
  • reactive定义的响应式数据是深层次的
  • 内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据都是响应式的
  • import {reactive} from 'vue'
    

8. vue3响应式原理

8.1 vue2响应式原理

  • 实现原理

    • 对象类型:通过 Object.defineProperty() 对属性的读取、修改进行拦截(数据劫持)

    • 数组类型:通过重写更新数组的一系列方法来实现拦截

      Object.defineProperty(data,'count',{
         	get(){},
          set(){}
      });
      
  • 存在的问题

    • 新增属性、删除属性,界面不会更新
    • 直接通过数组下标修改数组内容,界面不会更新

8.2 vue3响应式原理

  • 实现原理
    • 通过Proxy(代理):拦截对象中任意属性的变化,包括(属性值读写、属性添加、属性删除等)
    • 通过Reflect(反射):对被代理对象的属性进行操作
    • MDN文档中描述的Proxy与Reflect
      • Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
      • Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
  • Proxy
new Proxy(data,{
    //有人读取data的某个属性时调用
    get(target,attributeName){
        return target[attributeName];
    },
    //有人修改data属性或者给data追加属性时调用
    set(target,attributeName,value){
        target[attributeName] = value;
    },
    //有人删除data上某个属性时调用
    deleteProperty(target,attributeName){
		return delete target[attributeName];
    }
});
  • Reflect
    • ECMA组织正在尝试将Object上的属性方法移植到Reflect身上

9. reactive与ref对比

  1. 从定义数据角度对比
    • ref 用来定义基本数据类型
    • reactive 用来定义对象或数组数据类型
    • ref 也可以定义对象或数组类型,但是最终内部会通过reactive转为代理对象
  2. 从原理的角度对比
    • ref 通过 Object.defineProperty()getset 来实现响应式
    • reactive 通过使用 Proxy 来实现响应式,并通过 Reflect 操作源对象内部的数据
  3. 从使用角度对比
    • ref 定义的数据,操作数据需要 .value,模板读取时不需要 .value
    • reactive 定义的数据,操作数据与读取数据都不需要 .value

10. setup的两个注意点

  1. setup的执行时机
    • 在beforeCreate之前执行一次,this是undefined
  2. setup的参数
    • props:值为对象,存储组件外部传递过来并且组件内部声明接收了的属性
    • context:上下文对象
      • attrs:值为对象,存储组件外部传递过来,但是没有在props中声明接收的属性。相当于 this.$attrs
      • slots:收到的插槽内容。相当于 this.$slots
      • emit:分发自定义事件的函数。相当于 this.$emit
props:['','',''...],
emits:['','',''...],
setup(props,context){
    ...
    return {
        ...
    }
}

11. computed计算属性

import {computed,reactive} from 'vue'
setup(){
	let person = reactive({
       firstName:'张',
       lastName:'三'
    });
    //简写形式,只考虑读取数据
    person.fullName = computed(()=>{
       return person.firstName + person.lastName; 
    });
    //完整形式,考虑读写
    person.fullName = computed({
       get(){
           return person.firstName + person.lastName; 
       },
       set(value){
           const nameArr = value.split('-');
           person.firstName = nameArr[0];
           person.lastName = nameArr[1] ;
       }
    });
    return {
        person
    }
}

与Vue2中的computed配置功能一致,只是需要写在setup函数中

12. watch监视属性

  • 与Vue2中的watch配置功能一致
  • 两个注意事项
    • 监视reactive定义的响应数据时,oldValue无法正确获取,强制开启了深度监视,deep属性失效
    • 监视reactive定义的响应数据中某个属性时,deep属性有效
import {ref,reactive,watch} from 'vue'
setup(){
    let sum = ref(0);
    let msg = ref('hello');
    let person = reactive({
       	name:'张三',
   		age:18,
        job:{
            name:'JavaEngineer',
            salary:20
        }
    });
    //监视ref所定义的一个响应式数据
    watch(sum,(newValue,oldValue)=>{
       console.log('sum改变了',newValue,oldValue); 
    });
    //监视ref所定义的多个响应式数据
    watch([sum,msg],(newValue,oldValue)=>{
       console.log('sum改变了',newValue,oldValue); 
    },{immediate:true});
    //监视reactive所定义的一个响应式数据,此处oldValue无法正常获取,deep属性失效
    watch(person,(newValue,oldValue)=>{
       console.log('person改变了',newValue,oldValue); 
    },{deep:false});
    //监视reactive所定义的一个响应式数据的某个普通属性,此处oldValue可以正常获取,deep属性有效
    watch(()=>person.age,(newValue,oldValue)=>{
       console.log('person.age改变了',newValue,oldValue); 
    });
    //监视reactive所定义的一个响应式数据的某些普通属性
    watch([()=>person.age,()=>person.name],(newValue,oldValue)=>{
       console.log('person.age或name改变了',newValue,oldValue); 
    });
    //监视reaction所定义的一个响应式数据中的对象属性,deep属性有效
    watch(()=>person.job,(newValue,oldValue)=>{
       console.log('person.job改变了',newValue,oldValue); 
    },{deep:true});
    return {
        sum,age,person
    }
}
  • watchEffect函数

    • watch的套路是:既要指明监视的属性,也要指明监视的回调
    • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,就监视哪个属性
    • watchEffect有点类似与computed
      • 但是computed注重的是计算出来的值(回调函数的返回值),所以必须需要写返回值
      • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值
    watchEffect(()=>{
        const x1 = sum.value;
        const v2 = person.value;
        console('watchEffect配置的回调执行了');
    });
    

13. Vue3生命周期

组件生命周期图示
  • Vue3中可以继续使用Vue2中的生命周期钩子,但是有两个被更名

    • beforeDestory 改名为 beforeUnmount

    • destoryed 改名为 unmounted

  • Vue3也提供了CompositionAPI形式的生命周期钩子函数,与Vue2对应关系如下:

    • beforeCreate ===> setup()
    • created ===> setup()
    • beforeMount ===> onBeforeMount()
    • mounted ===> onMounted()
    • beforeUpdate ===> onBeforeUpdate()
    • updated ===> onUpdated()
    • beforeUnmount ===> onBeforeUnmount()
    • unmounted ===> onUnmounted()
setup(){
    onBeforeMount(()=>{
        
    });
    onMounted(()=>{
        
    });
    onBeforeUpdate(()=>{
        
    });
    onUpdated(()=>{
        
    });
    onBeforeUnmount(()=>{
        
    });
    onUnmounted(()=>{
        
    });
}

14. 自定义hook

Vue中的hook:本质是一个函数,把setup函数中使用的CompositionAPI进行了封装

类似于Vue2中的mixin

自定义hook的优势:复用代码,让setup中的逻辑更清楚易懂

15. toRef与toRefs

  • 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性
  • 语法:const name = toRef(person,'name')
  • 应用场景:需要将响应式对象中的某个属性单独提供给外部使用时
  • 扩展:toRefs()toRefs() 的功能一致,但可以批量创建多个ref对象,语法为:toRefs(person)
return {
    person,
    ...toRefs(person)
}

16. CompositionAPI

16.1 shallowReactive与shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)
  • shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理
  • 应用场景:
    • 如果有一个对象数据,结构比较深,但变化时只是外层属性变化,可以使用shallowReactive
    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换,可以使用shallowRef

16.2 readonly与shallowReadonly

  • readonly:让一个响应式数据变为只读(深只读)
  • shallowReadonly:让一个响应式数据变为只读(浅只读)
  • 应用场景:不希望数据被修改

16.3 toRaw与markRaw

  • toRaw
    • 作用:将一个由 reactive 生成的响应式对象转为普通对象
    • 应用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面的更新
  • markRaw
    • 作用:标记一个对象,使其永远不会再成为响应式对象
    • 应用场景:
      • 有些值不应被设置为响应式,例如复杂的第三方类库等
      • 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能

16.4 customRef

  • 作用:创建一个自定义的 ref ,并对其依赖项跟踪和更新触发进行显式控制

  • 实现防抖效果

    <template>
      <p>
        This text only updates 1 second after you've stopped typing:
      </p>
      <p>{{ text }}</p>
      <input v-model="text" />
    </template>
    <script setup>
    import { useDebouncedRef } from './debouncedRef.js'
    const text = useDebouncedRef('hello', 1000)
    </script>
    
    import { customRef } from 'vue'
    
    export function useDebouncedRef(value, delay = 200) {
      let timeout
      return customRef((track, trigger) => {
        return {
          get() {
            track()//通知vue追踪value的变化
            return value
          },
          set(newValue) {
            clearTimeout(timeout)
            timeout = setTimeout(() => {
              value = newValue
              trigger()//通知vue重新解析模板
            }, delay)
          }
        }
      })
    }
    

16.5 provide与inject

img
  • 作用:实现祖孙组件间通信

  • 套路:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据

  • 具体写法

    • 祖组件

      setup(){
          ......
          let car = reactive({
              name:'奔驰',
              price:'40万'
          });
          provide('car',car)
          ......
      }
      
    • 后代组件

      setup(){
          ......
          const car = inject('car');
          return {
              car
          }
          ......
      }
      

16.6 响应式数据的判断

  • isRef:检查一个值是否为一个ref对象
  • isReactive:检查一个对象是否是由 reactive 创建的响应式代理对象
  • isReadonly:检查一个对象是否是由 readonly 创建的只读代理对象
  • isProxy:检查一个对象是否是由 reactive 或者 readonly 方法创建的代理对象

17. CompositionAPI优势

17.1 OptionAPI

img
img

17.2 CompositionAPI

【总结】1267- Vue2 迁移到 Vue3 到底要踩多少坑?_缓存
【总结】1267- Vue2 迁移到 Vue3 到底要踩多少坑?_缓存_02

18. Fragment组件

  • 在Vue2中,组件必须有一个根标签
  • 在Vue3中,组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
    • 好处:减少标签层级,减少内存占用

19. Teleport组件

  • Teleport 是一种能够将我们组件html结构移动到指定位置的技术

    <teleport to="移动位置">
        <div>我是一个弹窗</div>
    </teleport>
    

20. Suspense组件

  • 等待异步组件时渲染一些后备内容,获得更好的用户体验

  • 使用步骤

    • 异步引入组件

      import {defineAsyncComponent} from 'vue'
      const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
      
    • 使用 Suspense 包裹组件,并配置好 default fallback

      <template>
      	<div class="#app">
              <h3>我是App组件</h3>
              <Suspense>
              	<template v-slot:default>
                  	<Child/>
                  </template>
                  <template v-slot:fallback>
                  	<h3>加载中......</h3>
                  </template>
              </Suspense>
          </div>
      </template>
      

21. 收尾

21.1 全局API的转移

  • Vue2中有许多全局API和配置

    • 例如:注册全局组件,注册全局指令等
  • Vue3中对这些API做出了调整

    • 将全局的API,即 Vue.xxx 调整到应用实例 app

      Vue2 Vue3
      Vue.config.xxx app.config.xxx
      Vue.config.productionTip 移除
      Vue.component app.component
      Vue.directive app.directive
      Vue.mixin app.mixin
      Vue.use app.use
      Vue.prototype app.config.globalProperties
      Vue.config.keyCodes 移除

21.2 其他改变

  • data选项应该始终被声明为一个函数

  • 过渡类名的更改

    • Vue2.x写法

      .v-enter,.v-leave-to{
      	opacity: 0;
      }
      .v-leave,.v-enter-to{
          opacity: 1;
      }
      
    • Vue3.x写法

      .v-enter-from,v-leave-to{
          opacity:0;
      }
      .v-leave-from,v-enter-to{
          opacity:1;
      }
      
  • 移除 keyCode 作为 v-on 的修饰符,同时也不再支持 config.keyCodes

  • 移除 v-on.native 修饰符

    • 父组件中绑定事件

      <my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent"></my-component>
      
    • 子组件中声明自定义事件

      <script>
      	export default{
              emits:['close']
          }
      </script>
      
  • 移除过滤器 filter

    过滤器虽然看起来很方便,但是它需要一个自定义语法,打破大括号内表达式只是javascript的假设,这不仅有学习成本,而且有实现成本,建议用方法调用或计算属性去替换过滤器

原文作者:絷缘
作者邮箱:zhiyuanworkemail@163.com
原文地址:https://zhiyuandnc.github.io/Vr0OXGyK8/
版权声明:本文为博主原创文章,转载请注明原文链接作者信息