根据官方文档描述,Vue3.0性能的提升提升主要包括以下几个方面:
Object.definePropetry()
实现并且有些小瑕疵(对于对象属性的增加和删除),而Vue3.0则通过Es6Proxy
函数实现响应式。虚拟DOM
的实现和Tree-Shaking
setup
,ref
,reactive
,watch
,watchEffect
,provide
,inject
…我们还可以和之前一样用Vue-Clil来创建Vue3.0的工程,但是我们Vue3.0还可以使用vite来创建。
我在这里还是使用我们Vue-Cli来创建Vue3的工程
其实大概结构还是和Vue2是差不多的,首先我们先看一下入口文件
// Vue3引入了一个名为createAPP的工厂函数,并对外壳组件App进行挂载
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
之前我们在Vue2中,我们是通过new Vue()
的实例和渲染函数,通过$mount
对外壳组件进行挂载。
其他包括外壳组件App,和其他配置比如 vue.config.js等等都是几乎一样的,我们遇到的时候再说。
setup是Vue3的一个新的配置项,值为一个函数。包含我们需要用到的数据,方法,计算属性,生命周期等等。同时需要返回一个包含数据方法计算属性等等的对象或者一个渲染函数。
-------返回对象:
<template>
<div>我是App组件</div>
<h3>我的名字是{{person.name}}</h3>
<h3>我的年龄是{{person.age}}</h3>
<button @click="sayHello">点我SayHello</button>
</template>
<script>
export default {
name: "App",
setup() {
let person = {
name: "花生",
age: 23,
};
function sayHello() {
alert(`大家好,我的名字是${person.name},今年${person.age}岁了。`);
}
return { person, sayHello };
},
};
</script>
<template>
<div>我是App组件</div>
<!-- <h3>我的名字是{{person.name}}</h3>
<h3>我的年龄是{{person.age}}</h3> -->
<!-- <button @click="sayHello">点我SayHello</button> -->
</template>
<script>
import { h } from "@vue/runtime-core";
export default {
name: "App",
setup() {
// let person = {
// name: "花生",
// age: 23,
// };
// function sayHello() {
// alert(`大家好,我的名字是${person.name},今年${person.age}岁了。`);
// }
// return { person, sayHello };
return () => h("h2", "返回渲染函数");
},
};
</script>
当返回是渲染函数的时候,不管我们模板中写了什么,都会被渲染函数替换掉。
我们看到上面代码的时候很当然的就会想到,这些数据目前都没有响应式,那Vue3 的响应式是怎么实现的呢?
------ref:
在Vue2中我们使用ref为一个元素打标识,但是在Vue3 中我们的ref是一个函数,这两个可不是一个东西。而是在Vue3 中多出来一个ref函数。
<template>
<div>我是App组件</div>
<h3>我的名字是{{name}}</h3>
<h3>我的年龄是{{age}}</h3>
<button @click="changeInfo">修改人的信息</button>
</template>
<script>
export default {
name: "App",
setup() {
let name = "花生";
let age = 23;
function changeInfo() {
name = '土豆',
age = 32
console.log(name,age);
}
return {
name,
age,
changeInfo,
};
},
};
</script>
当我们点击按钮修改数据后,数据其实已经改了,但是vue并没有捕获到,就是说我们现在定义的数据根本就不是响应式。
1.ref定义基本类型数据
<template>
<div>我是App组件</div>
<h3>我的名字是{{name}}</h3>
<h3>我的年龄是{{age}}</h3>
<button @click="changeInfo">修改人的信息</button>
</template>
<script>
import { ref } from '@vue/reactivity';
export default {
name: "App",
setup() {
let name = ref("花生");
let age = ref(23);
function changeInfo() {
name.value = '土豆',
age.value = 32
console.log(name,age);
}
return {
name,
age,
changeInfo,
};
},
};
</script>
通过ref函数返回的其实是一个RefImpl
(reference Implement)引用对象的实例对象,
其实,ref函数在实现基本类型的响应式的时候也是和Vue2的实现方法一样,通过Object.defineProperty()
的get/set
进行数据劫持来实现的。
2.ref定义对象类型数据
<template>
<div>我是App组件</div>
<h3>我的名字是{{name}}</h3>
<h3>我的年龄是{{age}}</h3>
<h3>我的工作是{{job.type}}</h3>
<h3>我的薪水是{{job.salary}}</h3>
<button @click="changeInfo">修改人的信息</button>
<button @click="changeSalary">修改人的薪水</button>
</template>
<script>
import { ref } from '@vue/reactivity';
export default {
name: "App",
setup() {
let name = ref("花生");
let age = ref(23);
let job = ref({
type:"前端工程师",
salary:20
})
function changeInfo() {
name.value = '土豆',
age.value = 32
}
function changeSalary() {
job.value.type = "Java工程师"
job.value.salary ++
console.log(job);
}
return {
name,
age,
job,
changeInfo,
changeSalary
};
},
};
</script>
Vue3.0在处理对象类型的数据响应式时,使用的并不是Object.defineProperty()
而是底层通过Es6的Proxy
实现响应式。
setup() {
let person = reactive({
name: "花生",
age: 23,
job: {
type: "前端工程师",
salary: 20,
},
});
function changeInfo() {
person.name = "土豆";
person.age = 32;
}
function changeSalary() {
person.job.type = "Java工程师";
person.job.salary++;
console.log(person);
}
return {
person,
changeInfo,
changeSalary,
};
},
其实,我们使用ref来定义对象类型数据的时候,Vue3也是通过reactive来实现响应式的。
1.回顾一下Vue2 的响应式实现。
<html>
<body>
<input id="input" name="value" type="text">
<br>
输入的是:<h3 id="myInput"></h3>
<script>
var data = {}
document.getElementById('input').oninput = function (e) {
data.name = e.target.value
}
Object.defineProperty(data, 'name', {
get: function () {
return data.name
console.log("data被读取了");
},
set: function (val) {
document.getElementById('myInput').innerHTML = val
console.log("data被修改了");
}
})
</script>
</body>
</html>
通过Object.defineProperty()
的get/set进行数据劫持实现响应式。
2.Vue3.0实现响应式
Vue3.0对于基本类型使用ref函数实现响应式,原理还是之前的Object.defineProperty()
的get/set 数据劫持。
我们这里主要说Vue3对于其他类型的数据的响应式的处理,Vue3在这里并没有跟之前一样使用Object.defineProperty()
而是使用Es6的一个在Window上的新方法:Proxy
。
Proxy(代理)介绍
//target:需要被代理的对象
//handler:也是一个对象,用来定义被代理的行为
let proxy = new Proxy(target,handler)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue3响应式</title>
</head>
<body>
<script>
let person = {
name: "花生",
age: 23
}
let p = new Proxy(person, {})
</script>
</body>
</html>
Reflect
ES6 中将 Object 的一些明显属于语言内部的方法移植到了 Reflect 对象上。
与Proxy相对应。我们的Vue3的响应式就可以这样实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue3响应式</title>
</head>
<body>
<script>
let person = {
name: "花生",
age: 23
}
let p = new Proxy(person, {
get(target, propName) {
console.log(`p身上的${propName}属性被读取了`);
return Reflect.get(person, propName)
},
set(target, propName, value) {
console.log(`p身上的${propName}属性被修改了,要去更新页面了!`);
Reflect.set(person, propName, value)
},
deleteProperty(target, propName) {
console.log(`p身上的${propName}属性被删除了`);
return Reflect.deleteProperty(target, propName)
},
})
</script>
</body>
</html>
export default {
name: "Test",
props: {
job: {
type: String,
default: "",
},
salary: {
type: Number,
default: 0,
},
},
emits: ["sayHello"],
setup(props, context) {
console.log(props, context);
let person = reactive({
name: "花生",
age: 23,
});
function sayHello() {
context.emit("sayHello", person.name);
}
return {
person,
sayHello,
};
},
};
第一个参数props:值为对象,都组件外部传进来且组件内部声明接收了的参数
第二个参数context:
1.attrs:值为对象,由组件外部传进来但是组价内部没有声明接收的属性。
2.emit:分发自定义事件函数。
3.slots:收到的插槽内容(虚拟DOM)。
<template>
<div class="app">
<h3>我是App组件</h3>
姓:<input v-model="person.fristName">
<br>
名:<input v-model="person.lastName">
<br>
全名:{{person.fullName}}
</div>
</template>
<script>
import { reactive } from "@vue/reactivity";
import { computed } from "@vue/runtime-core";
export default {
name: "App",
setup() {
let person = reactive({
fristName: "花",
lastName: "生",
});
//简写形式(只读)
person.fullName = computed(() => {
return person.fristName + "-" + person.lastName;
});
return {
person,
};
},
};
</script>
<style scoped>
.app {
background: orange;
padding: 10px;
height: 300px;
}
</style>
person.fullName = computed({
get() {
return person.fristName + "-" + person.lastName;
},
set(value) {
let nameArr = value.split("-");
person.fristName = nameArr[0];
person.lastName = nameArr[1];
},
});
<template>
<div class="app">
<h3>我是App组件</h3>
<h3>{{sum}}</h3><br>
<button @click="sum++">点我+1</button>
</div>
</template>
<script>
import { ref } from "@vue/reactivity";
import { watch } from "@vue/runtime-core";
export default {
name: "App",
setup() {
let sum = ref(0);
watch(sum, (newValue, oldValue) => {
console.log(`sum改变`, newValue, oldValue);
},{immediate:true});
return {
sum,
};
},
};
</script>
<style scoped>
.app {
background: orange;
padding: 10px;
height: 300px;
}
</style>
watch([sum, name], (newValue, oldValue) => {
console.log(`sum或name改变`, newValue, oldValue);
});
watch(person, (newValue, oldValue) => {
console.log(`person改变`, newValue, oldValue);
});
newValue和oldValue值相同,同时默认强制开启了深度监听
watch(()=>person.age, (newValue, oldValue) => {
console.log(`person.age改变`, newValue, oldValue);
});
watch(
() => [person.age, person.name],
(newValue, oldValue) => {
console.log(`person.age改变`, newValue, oldValue);
}
);
watchEffect(() => {
const x1 = person.name;
const x2 = person.age;
console.log(`watchEffect被调用了!`);
});
整体观察,我们发现,Vue3.0因为先判断了是否挂载成功而少了一个判断环节,也减少了两个生命周期钩子(brforeCreate,created)资源的浪费。同时,beforeUNmount和unmounted的名字的更改。
我们来实际体验一下各个生命周期函数被调用的顺序。
Demo.vue
<template>
<div class="demo">
<h3>我是App组件</h3>
<h3>{{sum}}</h3>
<button @click="sum++">点我+1</button>
</div>
</template>
<script>
import { reactive, ref } from "@vue/reactivity";
export default {
name: "App",
setup() {
let sum = ref(0);
return {
sum,
};
},
beforeCreate() {
console.log("------beforeCreate------");
},
created() {
console.log("------created------");
},
beforeMount() {
console.log("------beforeMount------");
},
mounted() {
console.log("------creamountedted------");
},
beforeUpdate() {
console.log("------beforeUpdate------");
},
updated(){
console.log("------updated------");
},
beforeUnmount(){
console.log("------beforeUnmount------");
},
unmounted(){
console.log("------unmounted------");
}
};
</script>
<style scoped>
.demo {
background: orange;
padding: 10px;
height: 200px;
}
</style>
App.vue
<template>
<div class="app">
<Demo v-if="isShowDemo" />
<button @click="isShowDemo = !isShowDemo">是否展示Demo</button>
</div>
</template>
<script>
import { reactive, ref } from "@vue/reactivity";
import Demo from "./components/Demo.vue";
export default {
components: { Demo },
name: "App",
setup() {
let isShowDemo = ref(true);
return {
isShowDemo,
};
},
};
</script>
<style scoped>
.app {
background: gray;
padding: 10px;
height: 300px;
}
</style>
Demo初始化挂载时
<template>
<div class="demo">
<h3>我是App组件</h3>
<h3>{{sum}}</h3>
<button @click="sum++">点我+1</button>
</div>
</template>
<script>
import { reactive, ref } from "@vue/reactivity";
import {
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onMounted,
onUnmounted,
onUpdated,
} from "@vue/runtime-core";
export default {
name: "App",
setup() {
console.log("------setup------");
let sum = ref(0);
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 {
sum,
};
},
};
</script>
<style scoped>
.demo {
background: orange;
padding: 10px;
height: 200px;
}
</style>
本身是一个函数,对setup()中的组合式API进行了封装,实现代码复用,类似于Vue2.x的mixin。我们还是用最经典的例子——屏幕打点。
Demo.vue
<template>
<div class="demo">
<h3>我是Demo组件</h3>
<h3>打点坐标是: x:{{point.x}},y:{{point.y}}</h3>
</div>
</template>
<script>
import { reactive, ref } from "@vue/reactivity";
import { onBeforeUnmount, onMounted } from "@vue/runtime-core";
import usePoint from "../hooks/usePoint";
export default {
name: "App",
setup() {
let point = usePoint();
return {
point,
};
},
};
</script>
<style scoped>
.demo {
background: orange;
padding: 10px;
height: 200px;
}
</style>
hooks—>usePoint.js
import { reactive, ref } from "@vue/reactivity";
import { onBeforeUnmount, onMounted } from "@vue/runtime-core";
export default function () {
let point = reactive({
x: 0,
y: 0,
});
function getPoint(event) {
point.x = event.pageX;
point.y = event.pageY;
console.log(point.x, point.y);
}
onMounted(() => {
window.addEventListener("click", getPoint);
});
onBeforeUnmount(() => {
window.removeEventListener("click", getPoint);
});
return point
}
<template>
<div class="app">
<h3>我是App组件</h3>
<h4>{{person}}</h4>
<h3>姓名:{{name}}</h3>
<h3>年龄:{{age}}</h3>
<h3>工作:{{salary}}块大洋</h3>
<button @click="name+='~'">修改姓名</button>
<button @click="age++">年龄+1</button>
<button @click="salary++">涨薪</button>
</div>
</template>
<script>
import { reactive, ref, toRef } from "@vue/reactivity";
export default {
name: "App",
setup() {
let person = reactive({
name: "花生",
age: 23,
job: {
salary: 20,
},
});
return {
person,
name:toRef(person,'name'),
age:toRef(person,'age'),
salary:toRef(person.job,'salary')
};
},
};
</script>
<style scoped>
.app {
background: orange;
padding: 10px;
height: 300px;
}
</style>
<template>
<div class="app">
<h3>我是App组件</h3>
<h4>{{person}}</h4>
<h3>姓名:{{name}}</h3>
<h3>年龄:{{age}}</h3>
<h3>工作:{{job.salary}}块大洋</h3>
<button @click="name+='~'">修改姓名</button>
<button @click="age++">年龄+1</button>
<button @click="job.salary++">涨薪</button>
</div>
</template>
<script>
import { reactive, ref, toRef, toRefs } from "@vue/reactivity";
export default {
name: "App",
setup() {
let person = reactive({
name: "花生",
age: 23,
job: {
salary: 20,
},
});
return {
person,
...toRefs(person)
};
},
};
</script>
<style scoped>
.app {
background: orange;
padding: 10px;
height: 300px;
}
</style>
浅层次的实现响应式,接着上面的例子,使用shallowReactive
setup() {
let person = shallowReactive({
name: "花生",
age: 23,
job: {
salary: 20,
},
});
return {
person,
...toRefs(person)
};
},
name
,age
都是响应式数据,但是深层次的salary
则没有响应式。
ref对于对象类型数据借助了reactive,shallowRef而不对对象类型的数据进行响应式处理。
setup() {
let refX = ref({
x:0
})
let shallowRefX = shallowRef({
x:0
})
console.log('ref',refX);
console.log('shallowRef',shallowRefX);
let person = shallowReactive({
name: "花生",
age: 23,
job: {
salary: 20,
},
});
return {
person,
...toRefs(person)
};
},
使用readonly之后,无论是浅层次的,还是深层次的的数据都不能被修改。
setup() {
let sum = ref(0);
let person = reactive({
name: "花生",
age: 23,
job: {
salary: 20,
},
});
person = readonly(person)
return {
sum,
...toRefs(person),
};
},
使用readonly之后,第一层次的数据不能被修改,但是深层次的的数据时可以被修改的。
将reactive定义的响应式数据类型,抓换成普通的对象。
<template>
<div class="app">
<h3>我是App组件</h3>
<h3>求和为:{{sum}}</h3>
<hr>
<h3>姓名:{{name}}</h3>
<h3>年龄:{{age}}</h3>
<h3>工作:{{job.salary}}块大洋</h3>
<button @click="name+='~'">修改姓名</button>
<button @click="handleAgeChange">年龄+1</button>
<button @click="job.salary++">涨薪</button>
</div>
</template>
<script>
import { reactive, ref, toRaw, toRefs } from "@vue/reactivity";
export default {
name: "App",
setup() {
let sum = ref(0);
let person = reactive({
name: "花生",
age: 23,
job: {
salary: 20,
},
});
function handleAgeChange() {
person.age++;
console.log(toRaw(person));
}
return {
sum,
...toRefs(person),
handleAgeChange
};
},
};
</script>
<style scoped>
.app {
background: orange;
padding: 10px;
height: 300px;
}
</style>
被标记的对象,将不会变成响应式数据。
<template>
<div class="app">
<h3>我是App组件</h3>
<h3>求和为:{{sum}}</h3>
<hr>
<h3>姓名:{{name}}</h3>
<h3>年龄:{{age}}</h3>
<h3>工作:{{job.salary}}块大洋</h3>
<h3>车辆信息:{{person.car}}</h3>
<button @click="name+='~'">修改姓名</button>
<button @click="age++">年龄+1</button>
<button @click="job.salary++">涨薪</button>
<button @click="person.car.name+='!'">车改名</button>
<button @click="person.car.price++">车涨价</button>
</div>
</template>
<script>
import { markRaw, reactive, ref, toRaw, toRefs } from "@vue/reactivity";
export default {
name: "App",
setup() {
let sum = ref(0);
let person = reactive({
name: "花生",
age: 23,
job: {
salary: 20,
},
});
person.car = markRaw({
name:'跑跑卡丁车',
price:40
})
console.log(person);
return {
sum,
person,
...toRefs(person),
};
},
};
</script>
<style scoped>
.app {
background: orange;
padding: 10px;
height: 400px;
}
</style>
自定义ref,官方例子
<template>
<div class="app">
<h3>我是App组件</h3>
<input
type="text"
v-model="keyWord"
>
<h3>{{keyWord}}</h3>
</div>
</template>
<script>
import { customRef, ref } from "@vue/reactivity";
export default {
name: "App",
setup() {
let keyWord = myRef("hello", 1000);
function myRef(value, delay) {
return customRef((track, trigger) => {
return {
get() {
console.log("数据被读取了");
track(); //通知vue数据需要被追踪
return value;
},
set(newValue) {
console.log("数据被修改了", newValue);
value = newValue;
setTimeout(() => {
trigger(); //通知vue去更新模板
}, delay);
},
};
});
}
return {
keyWord,
};
},
};
</script>
<style scoped>
.app {
background: orange;
padding: 10px;
height: 200px;
}
</style>
用于实现组件间的通信,尤其是祖孙组件之间。祖先组件使用provide提供数据,后代组件通过inject注入来获取数据。
App.vue
name: "App",
setup() {
let person = reactive({
name: "花生",
age: 23,
});
provide("info", person);
return {
person,
};
},
Test.vue
name: "Test",
setup() {
let person = inject("info");
let car = inject("car");
return {
person,
car,
};
},
Vue3.0新增了Fragment组件。在Vue2.x中我们只能有一个根标签,而在Vue3,0中我们可以写多个根标签,然后Vue3将其包在Fragment这一虚拟元素中。
Teleport 提供了一种干净的方法, 让组件的html在父组件界面外的特定标签(很可能是body)下插入显示
它们允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验