天命不足畏,祖宗不足法。 ——王安石
本文并非标题党,而是实实在在的硬核文章,如果有想要学习Vue3的网友,可以大致的浏览一下本文,总体来说本篇博客涵盖了Vue3中绝大部分内容,包含常用的CompositionAPI(组合式API)、其它CompositionAPI以及一些新的特性:Fragment、Teleport、Suspense、provide和inject。
既然是学习Vue3,那么首先应该需要的是如何初始化项目,在这里提供了两种方式供大家参考
官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create
// 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
// 安装或者升级你的@vue/cli
npm install -g @vue/cli
// 创建
vue create vue_test
// 启动
cd vue_test
npm run serve
vite官网:https://vitejs.cn/
// 创建工程
npm init vite-app <project-name>
// 进入工程目录
cd <project-name>
// 安装依赖
npm install
// 运行
npm run dev
这里的项目目录结构分析主要是main.js文件
Vue2里面的main.js
new Vue({
el: '#app',
components: {},
template: ''
});
Vue3里面的main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
在Vue2里面,通过new Vue({})构造函数创建应用实例对象,而Vue3引入的不再是Vue的构造函数,引入的是一个名为createApp的工厂函数创建应用实例对象。
devtool:https://chrome.zzzmh.cn/info?token=ljjemllljcmogpfapbkkighbhhppjdbg
理解:Vue3.0中一个新的配置项,值为一个函数
setup是所有Composition API(组合式API)的入口
组件中所用到的数据、方法等等,均要配置在setup里面
setup函数的两种返回值
setup的执行时机
setup的参数
props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性
context:上下文对象
注意事项:
尽量不要与Vue2x的配置使用
setup不能是一个async函数,因为返回值不再是return的对象,而是Promise,模板看不到return对象中的属性,但是后期也可以返回一个Promise实例,需要Suspense和异步组件的配合
示例一:setup函数的两种返回值
<template>
<h2>练习setup相关内容</h2>
<!--<h2>setup返回一个对象,并使用对象中的属性和方法</h2>-->
<!--<p>姓名:{{student.name}}</p>-->
<!--<p>年龄:{{student.age}}</p>-->
<!--<button @click="hello">点击查看控制台信息</button>-->
<hr>
<h2>setup返回一个函数</h2>
</template>
<script>
import {h} from 'vue'
export default {
name: "setupComponent",
setup(){
// 属性
let student={
name:'张三',
age:18,
}
// 方法
function hello() {
console.log(`大家好,我叫${student.name},今年${student.age}`)
}
return{ // 返回一个对象
student,
hello,
}
// return()=>h('h1','你好') // 返回一个函数
}
}
</script>
<style scoped>
</style>
这里需要注意的是setup里面定义的属性和方法均要return出去,否则无法使用
示例二:setup里面的参数和方法和配置项混合使用
<template>
<h2>setup和配置项混用</h2>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>性别:{{sex}}</h2>
<button @click="sayHello">sayHello(Vue3里面的方法)</button>
<button @click="sayWelcome">sayWelcome(Vue2里面的方法)</button>
</template>
<script>
export default {
name: "setup01_component",
data(){
return{
sex:'男',
sum:0,
}
},
methods:{
sayWelcome(){
console.log(`sayWelcome`)
},
},
setup(){
let sum=100;
let name='张三';
let age=18;
function sayHello() {
console.log(`我叫${name},今年${age}`)
}
return{
name,
age,
sayHello,
test02,
sum
}
}
}
</script>
<style scoped>
</style>
这段代码是先实现了setup里面的属性和方法,以及Vue2中配置项里面的属性和方法。接下来添加对应的混合方法
<template>
<h2>setup和配置项混用</h2>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>性别:{{sex}}</h2>
<button @click="sayHello">sayHello(Vue3里面的方法)</button>
<button @click="sayWelcome">sayWelcome(Vue2里面的方法)</button>
<br>
<br>
<button @click="test01">测试Vue2里面调用Vue3里面的属性和方法</button>
<br>
<br>
<button @click="test02">测试Vue3setup里面调用Vue2里面的属性和方法</button>
<br>
<h2>sum的值是:{{sum}}</h2>
</template>
<script>
export default {
name: "setup01_component",
data(){
return{
sex:'男',
sum:0,
}
},
methods:{
sayWelcome(){
console.log(`sayWelcome`)
},
test01(){
console.log(this.sex); // Vue2里面的属性(data里面的属性)
// setup里面的属性
console.log(this.name);
console.log(this.age);
// setup里面的方法
this.sayHello();
}
},
setup(){
let sum=100;
let name='张三';
let age=18;
function sayHello() {
console.log(`我叫${name},今年${age}`)
}
function test02() {
// setup里面的属性
console.log(name);
console.log(age);
// data里面的属性
console.log(this.sex);
console.log(this.sayWelcome);
}
return{
name,
age,
sayHello,
test02,
sum
}
}
}
</script>
<style scoped>
</style>
这里新增了test01和test02方法,分别点击,控制台可以看到,点击配置项里面test01方法时,除了自身的sex属性有值,setup里面定义的属性也有值以及方法也可以调用,点击setup里面定义的test02方法时,控制台只能输出setup里面定义的属性和方法,而配置项里面定义的属性和方法值均为undefined。
示例三:setup的执行时机
setup会在beforeCreate之前执行一次
<template>
<h2>setup的执行机制</h2>
</template>
<script>
export default {
name: "setup_component03",
setup(){
console.log('setup')
},
beforeCreate(){
console.log('beforeCreate')
}
}
</script>
<style scoped>
</style>
查看控制台的话我们看到的顺序是setup>beforeCreate
setup里面context和props的使用
讲解setup这里面的两个参数之前,先回顾一下Vue2里面的相关知识
示例一:Vue2props和自定义事件的使用
准备两个组件,分别为parent.vue组件和child.vue组件
parent.vue
<template>
<div class="parent">
我是父组件
<child msg="传递信息" name="张三" @sendParentMsg="getMsg"/>
</div>
</template>
<script>
import Child from "./Child";
export default {
name: "Parent",
components: {Child},
methods:{
getMsg(msg){
console.log(msg)
}
}
}
</script>
<style scoped>
.parent{
padding: 10px;
background-color: red;
}
</style>
child.vue
<template>
<div class="child">
<h2>我是子组件</h2>
<p>父组件传递过来的消息是:{{msg}}</p>
<p>父组件传递过来的消息是:{{name}}</p>
<button @click="sendMsg">向父组件的传递信息</button>
</div>
</template>
<script>
export default {
name: "Child",
props:{
msg:{
type:String,
default:''
},
name:{
type:String,
default:''
}
},
mounted(){
console.log(this);
},
methods:{
sendMsg(){
this.$emit("sendParentMsg",'通知父组件更新')
}
}
}
</script>
<style scoped>
.child{
padding: 10px;
background-color: orange;
}
</style>
child组件对应的代码如下:
<template>
<div class="child">
<h2>我是子组件</h2>
<!--<p>父组件传递过来的消息是:{{msg}}</p>-->
<!--<p>父组件传递过来的消息是:{{name}}</p>-->
<p>父组件传递过来的消息是:{{$attrs.msg}}</p>
<p>父组件传递过来的消息是:{{$attrs.name}}</p>
<button @click="sendMsg">向父组件的传递信息</button>
</div>
</template>
<script>
export default {
name: "Child",
// props:{
// msg:{
// type:String,
// default:''
// },
// name:{
// type:String,
// default:''
// }
// },
mounted(){
console.log(this);
},
methods:{
sendMsg(){
this.$emit("sendParentMsg",'通知父组件更新')
}
}
}
</script>
<style scoped>
.child{
padding: 10px;
background-color: orange;
}
</style>
子组件通过props接收父组件传递的信息,通过this.$emit()自定义事件向父组件传递信息。当使用props接收数据的时候,attrs里面的数据为空,如果没有使用props接收数据的话,那么props里面就有值。
示例二:Vue2里面slot的使用
同理准备两个组件,一个Index.vue组件,另一个为MySlot.vue组件
Index.vue
<template>
<div class="index">
<h2>我是Index组件</h2>
<!--写法一-->
<my-slot>
<!--插槽里面的内容-->
<h2>传入的slot参数</h2>
<h2>传入的slot参数</h2>
<h2>传入的slot参数</h2>
<h2>传入的slot参数</h2>
</my-slot>
<!--写法二-->
<my-slot>
<template slot="header">
<h2>我是header组件</h2>
</template>
<template slot="footer">
<h2>我是footer附件</h2>
</template>
</my-slot>
</div>
</template>
<script>
import MySlot from "./MySlot";
export default {
name: "Index",
components: {MySlot}
}
</script>
<style scoped>
.index{
padding: 10px;
background: red;
}
</style>
MySlot.vue
<template>
<div class="slot">
<h2>我是MySlot组件</h2>
<slot></slot>
<br>
<slot name="header"></slot>
<br>
<slot name="footer"></slot>
</div>
</template>
<script>
export default {
name: "MySlot",
mounted(){
console.log(this);
}
}
</script>
<style scoped>
.slot{
padding: 10px;
background: orange;
}
</style>
作用:定义一个响应式数据
语法:const xxx=ref(initValue)
创建一个包含响应式数据的引用对象(reference对象);
JS中操作数据:xxx.value=xxx;
模板中读取数据:不需要.value,直接:
{{xxx}}
备注:
接收的数据可以是:基本类型,也可以是对象类型
基本类型的数据:响应式依然是靠Object.defineProperty()的get和set完成的
对象类型的数据:内部求助了Vue3.0中的一个新函数-reactive函数
示例
姓名:{{name}} 年龄:{{age}} 婚否:{{isMarry}} 爱好:{{hobby}} 证件类型:{{user.idCard}} 国籍:{{user.nation}}ref
ref定义基本数据类型
ref定义对象类型
注意:
作用:定义一个对象类型的响应式数据(基本类型别用它,用ref函数)
语法:const 代理对象=reactive(被代理的对象)接收一个对象(或数组),返回一个代理器对象(Proxy的实例对象,简称Proxy对象)
reactive定义的响应式数据是深层次的
内部基于ES6的Proxy实现,通过代理对象的操作源对象的内部数据都是响应式的
姓名:{{student.name}} 年龄:{{student.age}} 爱好:{{student.hobbies}}reactive响应式数据
从定义数据的角度对比
从原理角度对比
从使用角度
// attr表示需要监视的属性
// 情况一:监视单个ref定义的响应式数据
watch(attr,(newValue,oldValue)=>{
console.log('attr变化了',newValue,oldValue);
})
// 情况二; 监视多个ref定义的响应式数据
watch([attr1,attr2,....,attrn],(newValue,oldValue)=>{
console.log('attr1或attrn变化了',newValue,oldValue);
})
// obj表示需要监听的对象
// 情况三:监视reactive定义的响应式数据
// 若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue
// 若watch监视的是reactive定义的响应式数据,则强制打开开启了深度监视
watch(obj,(newValue,oldValue)=>{
console.log('obj变化了',newValue,oldValue)
},{immediate:true,deep:false}); // 此处deep配置不在奏效
// 情况四,监视reactive定义的响应式数据中的某个属性
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})
// 情况五:监视reactive定义的响应式数据中的某一些属性
watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
})
// 特殊情况
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:false});// 此处由于是监视reactive所定义的对象中的某个属性,所以deep配置有效
watch
监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)
监视reactive定义的响应式数据中某个属性时deep配置有效
示例一:wath监听ref定义的响应式数据
示例二:watch监听reactive定义的响应式数据
<template>
<h1>watch监听reactive定义的响应式数据</h1>
<p>姓名:{{user.name}}</p>
<p>年龄:{{user.age}}</p>
<p>薪水:{{user.job.salary}}K</p>
<button @click="user.name+='!'">改变姓名</button>
<button @click="user.age++">改变年龄</button>
<button @click="user.job.salary++">改变薪水</button>
</template>
<script>
import {watch,reactive} from 'vue'
export default {
name: "watch_component02",
setup(){
let user=reactive({
name:'张三',
age:18,
job:{
salary:20
}
});
// 情况一:监听reactive定义的响应式数据,无法正确获取oldValue
/**
* 此时的newValue和oldValue都是最新的数据
* 默认强制开启深度监视,此时深度监视失效
*/
watch(user,(newValue,oldValue)=>{
console.log(newValue,oldValue);
},{deep:false});
// 情况二,监视reactive定义的响应式数据的单个属性
// watch(()=>user.name,(newValue,oldValue)=>{
// console.log('name发生了变化',newValue,oldValue);
// });
// watch(()=>user.age,(newValue,oldValue)=>{
// console.log('age发生了变化',newValue,oldValue);
// })
// 情况三:监视reactive定义的响应式数据的多个属性
/**
* newValue和oldValue都是数组的形式,其中数组的第n位表示监听位置的第n位
* 例如:此例子中,监听属性的第一位是userName,那位newValue和oldValue对应的第一位也是
* userName,
*/
// watch([()=>user.name,()=>user.age],(newValue,oldValue)=>{ // 写法一
// console.log('name或age中的某个属性发生了变化',newValue,oldValue);
// })
// watch(()=>[user.name,user.age],(newValue,oldValue)=>{ // 写法二
// console.log('name或者age中的某个属性发生了变化',newValue,oldValue)
// })
// 情况四:监视reactive定义的响应式数据的对象的某个属性,此时deep有效
/**
* 注意:此时需要区别是reactive定义的对象还是reactive定义的对象里面的某个属性
* 此时deep有效,关闭了监视
*/
// watch(()=>user.job,(newValue,oldValue)=>{
// console.log(newValue,oldValue);
// },{deep:false});
return{
user
}
}
}
</script>
<style scoped>
</style>
watchEffect
watch的套路是:既要指明监视的属性,也要指明监视的回调
watchEffect的套路是:不用指明监视那个属性,监视的回调中用到那个属性,那就监视那个属性
watchEffect有点像computed
但computed注重的是计算出来的值(回调函数的返回值),所以必需要写返回值
而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值
示例
在Vue2中主要是通过数据劫持来实现响应式原理的,也就是基于Object.defineProperty来实现的,而Vue3中是通过Proxy和Reflect来实现的(个人最低层次上的理解,还望各位大佬海涵)。
那么我们来对比一下Vue3实现响应式的原理比Vue2实现响应式的原理好在哪里?
实现原理
Object.defineProperty(data,'count',{
get(){}
set(){}
})
存在问题
新增属性、删除属性、界面不会自动更新
直接通过下标修改数组,界面不会自动更新
先看一个简单的示例
分别调用按钮对应的方法,控制台可以看到,新增性别属性时,student里面有性别这个属性,但是并没有实现响应式(视图没有更新),同理,其它两个按钮对应的方法也是一样。
原因:Vue2.0想要实现响应式数据的话,必需先在data里面定义,之后重新添加的数据无法实现响应式。
解决方案:
新增/修改:vue.$set(target,propName/index,value)
删除:vue.$delete(target,propName/index)
此时我们修改对应的代码如下
addSex(){ // 新增性别
// this.student.sex='male';
// vm.$set(this.student,'sex','male');
this.$set(this.student,'sex',male);
console.log(this.student);
},
deleteAge(){ // 删除年龄
// delete this.student.age;
// this.$delete(this.student,'age');
vm.$delete(this.student,'age');
console.log(this.student);
},
updateHobbies(){ // 修改爱好
// this.student.hobbies[0]='玩游戏';
// this.$set(this.student.hobbies,0,'玩游戏');
// vm.$set(this.student.hobbies,0,'玩游戏');
/**
* 或者使用数组变异的方法
* push()
* pop()
* shift()
* unshift()
* splice()
* sort()
* reverse()
*/
this.student.hobbies.splice(0,1,'玩游戏');
console.log(this.student);
}
弊端
Object.defineProperty的简单示例
let student={
name:'张三',
age:18,
}
let p={}
Object.defineProperty(p,'name',{
get(){ // 读取name时触发
console.log('读取了name属性');
return student.name;
},
set(value){ // 修改name时触发
console.log('name发生了变化,视图发生了变化');
student.name=value;
}
});
console.log(p.name);
p.name='李四';
p.sex='male';
delete p.name;
关于Proxy和Reflect的用法这里不过多介绍,如果有想要了解的推荐看MDN或者阮一峰老师的ES6
示例
let user={
name:'张三',
age:18,
}
let p=new Proxy(user,{
get(target,propName){
console.log(`读取了p里面的${propName}属性`);
Reflect.get(target,propName);
// return target[propName];
},
set(target,propName,value){
console.log(`修改了p里面的${propName}属性`);
// target[propName]=value;
Reflect.set(target,propName,value);
},
deleteProperty(target,propName){
console.log(`删除了p里面的${propName}属性`);
// delete target[propName];
Reflect.deleteProperty(target,propName);
}
});
console.log(p.name);
p.name='李四';
p.sex='male';
delete p.age;
查看控制台,当读取name属性的时候触发get()方法,新增或者修改属性的时候触发set()方法,删除的时候触发deleteProperty()方法,这就是Vue3.0对响应式的改进。
Vue2和Vue3生命周期对比
beforeCreate
Not needed*
created
Not needed*
beforeMount
onBeforeMount
mounted
onMounted
beforeUpdate
onBeforeUpdate
updated
onUpdated
beforeUnmount
onBeforeUnmount
unmounted
onUnmounted
activated
onActivated
deactivated
onDeactivated
因为 setup是围绕 beforeCreate和 created生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup函数中编写。
Vue3x中可以继续使用Vue2x中的生命周期钩子,但有两个被更名
beforeDestory改名为beforeUnmout
destoryed改名为unmouted
Vue3x生命周期和钩子函数
数字:{{num}}
作为初学hook的我来说,我对于hook并没有了解多少,这里贴一些自己练习中的示例,现阶段的我感觉不出来export 导出函数和hook的区别和优点。示例的话是鼠标点击的时候获取当前坐标
示例
<template>
<h2>自定义hook</h2>
<h2>当前x的坐标:{{x}},当前y的坐标:{{y}}</h2>
</template>
<script>
import {reactive,toRefs,onMounted,onUnmounted} from 'vue'
export default {
name: "hook_component01",
setup(){
// 数据
let point=reactive({
x:0,
y:0,
})
// 方法
function getPoint(event){
console.log(event)
point.x=event.clientX;
point.y=event.clientY;
}
// 生命周期钩子函数
onMounted(()=>{
window.addEventListener('click',getPoint);
})
onUnmounted(()=>{
window.removeEventListener('click',getPoint);
})
return{
...toRefs(point)
}
}
}
</script>
<style scoped>
</style>
抽离单独的hook
usePoint.js
import {reactive,onMounted,onUnmounted} from 'vue'
export let getPoint=()=>{
// 数据
let point=reactive({
x:0,
y:0,
})
// 方法
function getPoint(event){
console.log(event)
point.x=event.clientX;
point.y=event.clientY;
}
// 生命周期钩子函数
onMounted(()=>{
window.addEventListener('click',getPoint);
})
onUnmounted(()=>{
window.removeEventListener('click',getPoint);
})
return point
}
需要引入hook的.vue文件
import {reactive,toRefs,onMounted,onUnmounted} from 'vue'
import {getPoint} from "./hook/usePoint";
export default {
name: "hook_component01",
setup(){
let point=getPoint();
return{
...toRefs(point)
}
}
}
这个就是最简单hook的用法,如果有知道export 导出函数和hook区别的大佬,可以在下方评论区留言,感激不敬!!!
toRef
示例一
<template>
<h2>toRef与toRefs</h2>
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h2>薪水:{{person.job.salary}}k</h2>
<button @click="person.name+='!'">修改姓名</button>
<button @click="person.age++">修改年龄</button>
<button @click="person.job.salary++">涨点薪资</button>
</template>
<script>
import {reactive} from 'vue'
export default {
name: "toRef_component",
setup(){
let person=reactive({
name:'二郎神杨杨戬',
age:18,
job:{
salary:20
}
})
return{
person,
}
}
}
</script>
<style scoped>
</style>
示例一里面直接返回person对象,导致每次取值的时候都需要person.xxx,这样既不美观也不优雅,修改一下代码。
示例二
<template>
<h2>toRef与toRefs</h2>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>薪水:{{salary}}k</h2>
<button @click="name+='!'">修改姓名</button>
<button @click="age++">修改年龄</button>
<button @click="salary++">涨点薪资</button>
</template>
<script>
import {reactive,toRef} from 'vue'
export default {
name: "toRef_component",
setup(){
let person=reactive({
name:'二郎神杨杨戬',
age:18,
job:{
salary:20
}
})
return{
name:toRef(person,'name'),
age:toRef(person,'age'),
salary:toRef(person.job,'salary')
}
}
}
</script>
<style scoped>
</style>
错误用法示例一
<template>
<h2>toRef与toRefs</h2>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>薪水:{{salary}}k</h2>
<button @click="name+='!'">修改姓名</button>
<button @click="age++">修改年龄</button>
<button @click="salary++">涨点薪资</button>
</template>
<script>
import {reactive,toRef,toRefs} from 'vue'
export default {
name: "toRef_component",
setup(){
let person=reactive({
name:'二郎神杨杨戬',
age:18,
job:{
salary:20
}
})
return{
name:person.name,
age:person.age,
salary:person.job.salary
}
}
}
</script>
<style scoped>
</style>
错误用法示例二
<template>
<h2>toRef与toRefs</h2>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>薪水:{{salary}}k</h2>
<h2>peron对象{{person}}</h2>
<button @click="name+='!'">修改姓名</button>
<button @click="age++">修改年龄</button>
<button @click="salary++">涨点薪资</button>
</template>
<script>
import {reactive,toRef,toRefs,ref} from 'vue'
export default {
name: "toRef_component",
setup(){
let person=reactive({
name:'二郎神杨杨戬',
age:18,
job:{
salary:20
}
})
return{
person,
name:ref(person.name),
age:ref(person.age),
salary:ref(person.job.salary)
}
}
}
</script>
<style scoped>
</style>
toRefs
示例一
<template>
<h2>toRef与toRefs</h2>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>薪水:{{job.salary}}k</h2>
<button @click="name+='!'">修改姓名</button>
<button @click="age++">修改年龄</button>
<button @click="job.salary++">涨点薪资</button>
</template>
<script>
import {reactive,toRef,toRefs} from 'vue'
export default {
name: "toRef_component",
setup(){
let person=reactive({
name:'二郎神杨杨戬',
age:18,
job:{
salary:20
}
})
return{
...toRefs(person)
}
}
}
</script>
<style scoped>
</style>
shallowReactive:只处理对象最外层属性的响应式(浅响应式)
shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理
什么时候用
shallowRef示例
<template>
<h2>shallowRef示例</h2>
<h2>当前sum的值是:{{sum}}</h2>
<button @click="sum++">点我sum加1</button>
<h2>当前x.y的值是:{{x.y}}</h2>
<button @click="x.y++">点我x.y加1</button>
<button @click="x={y:100}">点我替换y的值</button>
</template>
<script>
import {shallowRef,ref} from 'vue'
export default {
name: "shallowRef_component",
setup(){
let sum=ref(0);
let x=shallowRef({
y:0,
})
return{
sum,
x,
}
}
}
</script>
<style scoped>
</style>
这里我们通过ref和shallowRef进行比对,点击x.y加1按钮的时候,视图不会触发更新,因为y的值对象作为深层次的,而直接点击sum加1的按钮的时候可以触发更新,sum直接是浅层次的,替换y的值的时候替换的是整个x的值(即整个对象),而不是x里面的值进行操作。
shallowReactive示例
<template>
<h2>shallowReactive示例</h2>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>薪资:{{job.salary}}k</h2>
<button @click="name+='!'">修改姓名</button>
<button @click="age++">修改年龄</button>
<button @click="job.salary++">涨点薪资</button>
</template>
<script>
import {shallowReactive,toRefs} from 'vue'
export default {
name: "shallowReactive01_component",
setup(){
let person=shallowReactive({
name:'张三',
age:18,
job:{
salary:20,
}
})
return{
...toRefs(person)
}
}
}
</script>
<style scoped>
</style>
点击修改姓名和修改年龄的按钮时,可以看到视图发生变化,点击涨薪的时候视图不会发生变化,但是数据发生了变化,这个大家可以使用控制台进行测试。
readonly:让一个响应式数据变为只读的(深只读)
shallowReadonly:让一个响应式变为只读的(浅只读)
示例一
readonly与shallowReadonly
姓名:{{name}}
年龄:{{age}}
薪资:{{job.salary}}
当前sum的值是:{{sum}}
使用readonly的时候,按钮点击全部失效,我们看下shallowReadonly的效果
使用shallowReadonly的时候,修改姓名,修改年龄都不会发生变化,只有涨薪发生了变化
toRaw
markRow
作用:标记一个对象,使其永远不会再成为响应式对象
应用场景:
有些值不应该被设置为响应式的,例如复杂的第三方类库,
当渲染具有不可变的数据源的大列表时,跳过响应式转换可以提高性能
示例一
toRaw与markRow
姓名:{{name}}
年龄:{{age}}
薪资:{{job.salary}}k
调用showRawPerson方法的时候,控制台可以看到输出最原始的person,sum的话,输出的undefined,toRaw对ref定义的响应式数据无效,接下来看下markRow的效果
示例二
<template>
<div style="width: 800px;margin: 0 auto">
<h2>toRaw与markRow</h2>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>薪资:{{job.salary}}k</h2>
<button @click="name+='!'">修改姓名</button>
<button @click="age++">修改年龄</button>
<button @click="job.salary++">涨点薪资</button>
<button @click="showRawPerson">输出最原始的person对象</button>
<h2>车的信息是:{{person.car}}</h2>
<button @click="addCar">给人添加一辆车</button>
<template v-if="person.car">
<button @click="person.car.name+='!'">修改车名</button>
<button @click="person.car.price++">修改车的价格</button>
<button @click="changeCarPrice">修改车的价格</button>
</template>
</div>
</template>
<script>
import {ref,reactive,toRaw,markRaw,toRefs} from 'vue'
export default {
name: "toRaw01_component",
setup(){
let sum=ref(0);
let person=reactive({
name:'二郎神杨戬',
age:18,
job:{
salary:20
}
})
function showRawPerson() {
let p=toRaw(person)
console.log(p);
let sum=toRaw(sum);
console.log(sum); // 对ref定义的响应式数据无效
}
function addCar() {
let car={name:'宝马',price:40}
person.car=markRaw(car);
}
function changeCarPrice() {
person.car.price++;
console.log(person.car.price)
}
return{
sum,
person,
...toRefs(person),
showRawPerson,
addCar,
changeCarPrice
}
}
}
</script>
<style scoped>
</style>
这里新增了一个车信息的方法和相关属性到person对象里面,正常情况下,直接在reactive里面的追加的数据会实现响应式的,但是这里使用了markRaw方法,所以点击修改车的名字和价格时数据发生了变化,但是视图不会更新。
作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制,它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数,并且应该返回一个带有 get 和 set 的对象。
实现防抖效果:
1、先实现自定义双向绑定
2、实现了双向绑定之后,实现防抖
自定义双向绑定
customRef示例
{{msg}}
在这里我们实现了数据的双向绑定,接下来是实现防抖
<template>
<div style="width: 800px;margin: 0 auto">
<h2>customRef示例</h2>
<input type="text" v-model="msg">
<h2>{{msg}}</h2>
</div>
</template>
<script>
import {customRef,} from 'vue'
export default {
name: "customRef01_component",
setup(){
function myRef(msg,delay){ // 自定义ref函数
let timer;
return customRef((track,trigger)=>{
return{
get(){
console.log('读取了值')
track();
return msg;
},
set(newValue){
timer=setTimeout(()=>{
console.log(`修改了值,修改后的值是:${newValue}`);
msg=newValue;
trigger();
},delay)
}
}
})
}
let msg=myRef('你好',500);
return{
msg
}
}
}
</script>
<style scoped>
</style>
祖组件
<template>
<h2>我是祖组件</h2>
<h3>汽车信息</h3>
<p>名称:{{name}}</p>
<p>价格:{{price}}</p>
<inject_component></inject_component>
</template>
<script>
import {reactive,toRefs,provide} from 'vue'
export default {
name: "provide_component",
setup(){
let car=reactive({
name:'宝马',
price:'40w'
});
provide('car',car); // 提供provide
return{
...toRefs(car)
}
}
}
</script>
<style scoped>
</style>
后代组件
<template>
<h2>我是孙组件</h2>
<h3>汽车信息</h3>
<p>名称:{{name}}</p>
<p>价格:{{price}}</p>
</template>
<script>
import {inject,toRefs,ref} from 'vue'
export default {
name: "inject_component",
setup(){
let car=inject("car"); //使用inject接收
return{
...toRefs(car)
}
}
}
</script>
<style scoped>
</style>
好处:减少标签层级,减少内存占用
Teleport是一种能够将我们的组件html结构移动到指定位置的技术
<teleport to='移动位置'>
<div v-if='isShow' class='mark'>
<div class="dialog">
<h3>我是一个弹窗</h3>
<button @click='isShow=true'>关闭弹窗</button>
</div>
</div>
</teleport>
实现一个弹窗居中显示,有四个组件,分别为:teleport_parent,teleport_child,teleport_son,teleport_dialog,然后需要实现的效果是在teleport_son组件引入teleport_dialog组件,teleport_dialog显示的时候在屏幕正中央
teleport_parent.vue
<template>
<div class="parent">
<h2>我是parent组件</h2>
<teleport_child/>
</div>
</template>
<script>
import Teleport_child from "./teleport_child";
export default {
name: "teleport_parent",
components: {Teleport_child}
}
</script>
<style scoped>
.parent{
background-color: red;
padding: 10px;
}
</style>
teleport_child.vue
<template>
<div class="child">
<h2>我是child组件</h2>
<teleport_son/>
</div>
</template>
<script>
import Teleport_son from "./teleport_son";
export default {
name: "teleport_child",
components: {Teleport_son}
}
</script>
<style scoped>
.child{
background-color: orange;
padding: 10px;
}
</style>
teleport_son.vue
<template>
<div class="son">
<h2>我是son组件</h2>
<teleport_dialog/>
</div>
</template>
<script>
import Teleport_dialog from "./teleport_dialog";
export default {
name: "teleport_son",
components: {Teleport_dialog}
}
</script>
<style scoped>
.son{
background-color: yellow;
padding: 10px;
}
</style>
teleport_dialog.vue
<template>
<div>
<button @click="isShow=true">点我弹窗</button>
<div class="dialog_container" v-if="isShow">
<h2>我是弹窗组件</h2>
<div class="dialog_body">
<h2>我是内容</h2>
<h2>我是内容</h2>
<h2>我是内容</h2>
<h2>我是内容</h2>
</div>
<button @click="isShow=false">关闭按钮</button>
</div>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
name: "teleport_dialog",
setup(){
let isShow=ref(false);
return{
isShow
}
}
}
</script>
<style scoped>
.dialog_container{
width: 500px;
height: 300px;
background: red;
}
</style>
实现的效果如下
当我们点击按钮的时候,效果是这样的
点击按钮的时候,弹窗显示,但是在son组件里面会改变son的高度,这样子不太美观,如果弹窗使用定位的话可以使其脱离文档流,而不会撑开son里面的高度,但是postion:absolute是根据最近的有定位元素的父元素进行定位的,所以此方法不可靠,我们需要实现的效果是根据body来定位
修改dialog的样式
<template>
<div>
<button @click="isShow=true">点我弹窗</button>
<teleport to="body">
<div class="mask" v-if="isShow">
<div class="dialog_container">
<h2>我是弹窗组件</h2>
<div class="dialog_body">
<h2>我是内容</h2>
<h2>我是内容</h2>
<h2>我是内容</h2>
<h2>我是内容</h2>
</div>
<button @click="isShow=false">关闭按钮</button>
</div>
</div>
</teleport>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
name: "teleport_dialog",
setup(){
let isShow=ref(false);
return{
isShow
}
}
}
</script>
<style scoped>
.mask{
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background: rgba(0,0,0,.5);
}
.dialog_container{
width: 500px;
height: 300px;
background: red;
position: absolute;
left: 50%;
top: 50%;
margin-left: -250px;
margin-top: -150px
}
</style>
作用:等待异步组件时渲染一些额外的内容,让应用有更好的用户体验
使用步骤:
异步引入组件
import {defineAsyncComponent} from 'vue'
const child=defineAsyncComponent(()=>import('./components/Child.vue'))
使用Suspense包裹组件,并配置好default和fallback
我是app组件
加载中……
示例
suspense.vue
<template>
<div class="suspense">
<h2>我是suspense组件</h2>
<child/>
</div>
</template>
<script>
import Child from "./child";
export default {
name: "suspense01_component",
components: {Child}
}
</script>
<style scoped>
.suspense{
background-color: red;
padding: 10px;
}
</style>
child.vue
<template>
<div class="child">
<h2>我是child组件</h2>
</div>
</template>
<script>
export default {
name: "child"
}
</script>
<style scoped>
.child{
background-color: orange;
padding: 10px;
}
</style>
如果使用以上代码引入的话,那么suspense和child组件将会同时加载,如果child里面还有其它子组件的话,子组件里面还有子组件,在网络慢的情况下,用户可能就看不到child组件以及child里面其它的组件。这会影响用户体验,修改对应的代码
suspense.vue
<template>
<div class="suspense">
<h2>我是suspense组件</h2>
<suspense>
<template v-slot:default>
<child/>
</template>
<template v-slot:fallback>
<h3>请稍等,加载中。。。</h3>
</template>
</suspense>
</div>
</template>
<script>
// import Child from "./child"; // 静态引入
import {defineAsyncComponent} from 'vue'
const Child=defineAsyncComponent(()=>import('./child')) // 异步引入
export default {
name: "suspense01_component",
components: {Child}
}
</script>
<style scoped>
.suspense{
background-color: red;
padding: 10px;
}
</style>
child.vue
<template>
<div class="child">
<h2>我是child组件</h2>
<h2>当前sum的值是:{{sum}}</h2>
</div>
</template>
<script>
import {ref} from 'vue'
export default {
name: "child",
setup(){
let sum=ref(0);
return new Promise((resole,reject)=>{
setTimeout(()=>{
resole({
sum
});
},3000)
})
}
}
</script>
<style scoped>
.child{
background-color: orange;
padding: 10px;
}
</style>
为了看到效果,延迟了3秒之后显示组件
vue2.x有许多全局API和配置,例如:全局注册组件、注册全局指令等
注册全局组件
Vue.component('MyButton',{
data:()=>{
count:0,
},
template:''
});
注册全局指令
Vue.directive('focus',{
inserted:el=>el.foucus
})
Vue3.0中对这些API做出了调整
将全局的API,即Vue.xxx调整到应用实例app上
2.x 全局API(Vue)
3.x 实例API(app)
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
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-to,
.v-enter-to{
opacity:1
}
父组件绑定事件
<my-component>
v-on:close="handleComponentEvent"
v-on:click="handleNativeClickEvent"
</my-component>
子组件中声明自定义组件
export default{
emits:['close']
}
移除过滤器(filter)
过滤器虽然看起来方便,但它需要一个自定义语法,打破大括号内表达式'只是javascript'的假设,这不仅有学习成本,而且有实现成 本, 建议用法调用或者计算属性去替换过滤器
相关库名称
在线地址
Vue 3.0 官方文档(英文)
Vue 3.0 中文文档
Composition-API手册
Vue 3.0 源码学习
Vue-Router 官方文档
Vuex 4.0
vue-devtools
Vite 源码学习
Vite 2.0 中文文档
Vue3 新动态
开源项目
开源项目
开源项目
相关库名称
在线地址
Vue 3.0 实战星座物语 H5 项目
Vue 3.0 UI 组件库开发
Vue 3.0 + Vite 手册阅读
Vue 3.0 入门之项目搭建(杨村长)
Vue 3.0 入门(技术胖)【不太建议推荐】
Vite 2.0 插件开发指南
Vue 3.0 + Vite 2.0 快速搭建 Electron 应用
Vue3.0视频(强烈推荐)
Vue3.0驾照题库
掘金地址:https://juejin.cn/post/6955129410705948702
Vue.js官网:https://v3.cn.vuejs.org/
bilibili地址:https://www.bilibili.com/video/BV1Zy4y1K7SH
MDN地址:https://developer.mozilla.org/zh-CN/
ES6地址:https://es6.ruanyifeng.com/
总体来说,学完这篇博客基本涵盖了Vue3中的绝大部分内容,写的也特别详细,笔记,配套资料和实战视频都有,主要看你愿不愿意抽出时间去学习,我自己认为学习是一辈子的事情,当然学习也是一件特别孤独的事情。如果本篇文章对您有所帮助的话,记得点赞、收藏和关注,原创不易,三连支持,感谢大家!
手机扫一扫
移动阅读更方便
你可能感兴趣的文章