一、vue3.0亮点
- Performance:性能比Vue 2. x快1.2-2倍
- Tree shaking support:按需编译,体积比Vue2. x更小
- Composition API:组合API (类似React Hooks)
- Better TypeScript support:更好的Ts支持
- Custom Renderer API:暴露了自定义道染API
- Fragment, Teleport (Protal), Suspense:更先进的组件
二、Vue3.0是如何变快的
1.diff算法优化:
- Vue2中的虚拟dom是进行全量的对比。第一次渲染的时候生成一颗dom树,数据发生改变时又生成一颗dom树,与之前的dom树进行全部比较,哪里不同更新哪里。
- Vue3新增了静态标记(PatchFlag),在与上次虚拟节点进行对比时候,只对比带有patch flag的节点,并且可以通过flag的信息得知当前节点要对比的具体内容。
vue3.0标记:
vue3.0源码中标记会改变的节点 :
https://vue-next-template-explorer.netlify.app/
2.hoistStatic 静态提升
- Vue2中无论元素是否参与更新,每次都会重新创建,然后再渲染
- Vue3中对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可
vue3.0源码将静态元素(即不会改变的元素)的创建提取为全局变量,渲染时复用,不会重新创建,性能提升:
3.cachellandlers事件侦听器缓存
- 默认情况下onClick会被视为动态绑定,会添加静态标志,所以每次都会去追踪它的变化,但是因为是同一个函数,所以没有必要追踪变化,直接缓存起来复用即可
未启用侦听器缓存时,被添加了静态标记,在diff算法中会被进行比较、追踪:
启用侦听器缓存,没有添加静态标记,性能提升:
4.ssr渲染
- 当有大量静态的内容时候,这些内容会被当做纯字符串推进一个buffer里面
即使存在动态的绑定,会通过模板插值嵌入进去,这样会比通过虚拟dmo来渲染的快上很多很多。
当静态内容大到一定量级时候,会用createStatielode方法在客户增去生成一个static node这些静态node,会被直接innerltml,就不需要创建对象,然后根据对象渲染。
三、创建vue3
1.创建vue3的三种方式
- Vue-Cli
- Webpack
- Vite
注意:vue版本应该在4.5.0以上
查看vue 版本号:
vue -V
2.Vite
Vite是Vue作者开发的意图取代webpack的工具。其实现原理是利用ES6的import会发送请求去加载文件的特性,拦截这些请求,做一些编译,省去webpack冗长的打包时间。
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
3.vue/cli
//安装
npm i -g @vue/cli
vue create 项目名
cd <project-name>
npm run serve
四、组合API
组合API:将当前数据和当前业务逻辑放在一起,方便管理,会自动注入到option API中。
beforeCreate:组件刚被创建出来,组件的data和methods还没初始化好;
created:组件刚被创建出来,组件的data和methods已经初始化好;
setup()是在beforeCreate之前执行的。所以setup()中无法使用data和methods,所以setup()中将this改为undefined。
setup()返回对象中的属性与data函数返回对象的属性合并为组件对象的属性,返回对象的方法与methods中的方法合并为对象方法。如有冲突,setup优先。
setup参数:setup(props,{attrs, slots, emit})
注意:setup()只能是同步的,不能是异步的。想使用异步应使用async await。
1.初始化基本类型使用ref
ref本质:通过给属性添加getter/setter来实现对数据的监听。
其实还是reactive,给ref函数传递一个值之后,ref函数底层会自动将ref转换成reactive。ref(1) -> reactive({value:1})
注意:在template中使用ref的值不用太过value获取;在js中使用必须通过value获取。
<template>
<div>
<h1>{{count}}</h1>
<button @click="add">增加</button>
</div>
</template>
<script>
import {ref} from 'vue';
export default {
name: 'App',
// setup函数是组合API的入口函数
setup() {
// 定义变量count的value值为1
let count = ref(1);
// 在组API中定义的变量/方法
function add() {
// count是一个对象
console.log(count); // RefImpl {_rawValue: 1, _shallow: false, __v_isRef: true, _value: 1}
// 改变count的值应该改变的是count的value属性值
count.value ++ ;
}
// 要想在外界使用,必须通过return暴露出去
return { count,add }
}
}
</script>
2.监听对象使用reactive
<template>
<ul>
<li v-for="(stu, index) in state.stus" :key="stu.id" @click="remStu(index)">{{stu.id}} - {{stu.name}} - {{stu.age}}</li>
</ul>
</template>
<script>
import { reactive } from 'vue';
export default {
name: 'HelloWorld',
setup() {
const stusWrap = {
stus: [
{id: 0, name: 'df0', age: 11},
{id: 1, name: 'df1', age: 14},
{id: 2, name: 'df2', age: 15},
]
})
// 监听对象或数组需要使用reactive
const state = reactive(stusWrap);
// 点击移除该项
function remStu(index) {
state.stus = state.stus.filter((stu, idx) => idx != index);
}
return { state, remStu };
}
}
</script>
reactive是Vue3中提供的实现响应式数据的方法,将传入的数据包装成Proxy对象。
- 在Vueq2中响应式数据是通过defineProperty来实现的。
- 在Vue3中响应式数据是通过RS6的Proxy来实现的。
stusWrap为目标对象,state为代理对象,
- 当改变目标对象的值时,界面不更新;
- 当改变代理对象的值时,界面更新,目标对象也更新
注意:reactive参数必须是对象(自定义对象/array)。如果给reactive传递了其他对象,默认情况下修改它,界面不会自动更新;需重新赋值才会更新。
<template>
<div>
<h1>{{state.time}}</h1>
<button @click="myFn">修改</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'ReactiveCom',
setup() {
let state = reactive({
time: new Date()
});
function myFn() {
// time修改自身,界面不会更新
// state.time.setDate(state.time.getDate() + 1);
// console.log(state.time);
// 需要给time重新赋值,界面才会更新
const newTime = new Date(state.time.getTime());
newTime.setDate(state.time.getDate() + 1);
state.time = newTime;
console.log(state.time);
}
return {state, myFn}
}
}
</script>
3.ref和reactive区别
(1)不同:
- 在template中若为ref类型的数据,vue会自动添加.value;
- 若为reactive类型,则不会自动添加.value。
通过当前数据是否有 __v_isRef 这个私有属性来判断,若 __v_isRef 为true,则为ref类型,则会自动添加.value。
我们可以通过isRef() 和 isReactive() 来判断。
<template>
<div>
<h1>{{count}}</h1>
<button @click="compare">比较</button>
</div>
</template>
<script>
import { ref, isRef, isReactive } from 'vue';
export default {
name: 'App',
setup() {
let count = ref(1);
function compare() {
console.log(isRef(count)); // true
console.log(isReactive(count)); // false
}
return { count,compare }
}
}
</script>
(2)相同:都是递归监听,数据无论多少层都能监听到,每一层都被封装为Proxy对象。但当数据量大时,非常消耗性能。
ref:
<template>
<div>
<h1>{{state.a}}</h1>
<h1>{{state.f.b}}</h1>
<h1>{{state.f.t.c}}</h1>
<h1>{{state.f.t.s.d}}</h1>
<button @click="myFn">显示</button>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
name: 'App',
setup() {
let state = ref({
a: 'a',
f: {
b: 'b',
t: {
c: 'c',
s: {
d: 'd'
}
}
}
});
function myFn() {
console.log(state); // RefImpl {_rawValue: {…}, _shallow: false, __v_isRef: true, _value: Proxy}
console.log(state.value); // Proxy {a: "a", f: {…}}
console.log(state.value.f); // Proxy {b: "b", t: {…}}
console.log(state.value.f.t); // Proxy {c: "c", s: {…}}
console.log(state.value.f.t.s); // Proxy {d: "d"}
state.value.a = 1;
state.value.f.b = 2;
state.value.f.t.c = 3;
state.value.f.t.s.d = 4;
}
return { state, myFn }
}
}
</script>
reactive:
<template>
<div>
<h1>{{state.a}}</h1>
<h1>{{state.f.b}}</h1>
<h1>{{state.f.t.c}}</h1>
<h1>{{state.f.t.s.d}}</h1>
<button @click="myFn">显示</button>
</div>
</template>
<script>
import { reactive } from 'vue';
export default {
name: 'App',
setup() {
let state = reactive({
a: 'a',
f: {
b: 'b',
t: {
c: 'c',
s: {
d: 'd'
}
}
}
});
function myFn() {
console.log(state); // Proxy {a: "a", f: {…}}
console.log(state.f); // Proxy {b: "b", t: {…}}
console.log(state.f.t); // Proxy {c: "c", s: {…}}
console.log(state.f.t.s); // Proxy {d: "d"}
state.a = 1;
state.f.b = 2;
state.f.t.c = 3;
state.f.t.s.d = 4;
}
return { state,myFn }
}
}
</script>
4.非递归监听
(1)shallowReactive:若更新数据第一层,则这个数据所有层的数据在界面都会更新,若没有更新第一层,则界面不会更新
<template>
<div>
<h1>{{state.a}}</h1>
<h1>{{state.f.b}}</h1>
<h1>{{state.f.t.c}}</h1>
<h1>{{state.f.t.s.d}}</h1>
<button @click="myFn">显示</button>
</div>
</template>
<script>
import { shallowReactive } from 'vue';
export default {
name: 'App',
setup() {
let state = shallowReactive({
a: 'a',
f: {
b: 'b',
t: {
c: 'c',
s: {
d: 'd'
}
}
}
});
function myFn() {
console.log(state); // Proxy {a: "a", f: {…}}
console.log(state.f); // {b: "b", t: {…}}
console.log(state.f.t); // {c: "c", s: {…}}
console.log(state.f.t.s); // {d: "d"}
state.a = 1; // shallow更新第一层,界面所有数据随之更新
state.f.b = 2; // 若没有更新第一层,则界面不更新
state.f.t.c = 3;
state.f.t.s.d = 4;
}
return { state,myFn }
}
}
</script>
(2)shallowRef:因为shallowRef只监听第一层即state.value的变化
shallowRef(10)的本质是转化成shallowReactive({value: 10}),所以监听的第一层是.value
<template>
<div>
<h1>{{state.a}}</h1>
<h1>{{state.f.b}}</h1>
<h1>{{state.f.t.c}}</h1>
<h1>{{state.f.t.s.d}}</h1>
<button @click="myFn">显示</button>
</div>
</template>
<script>
import { shallowRef } from 'vue';
export default {
name: 'App',
setup() {
let state = shallowRef({
a: 'a',
f: {
b: 'b',
t: {
c: 'c',
s: {
d: 'd'
}
}
}
});
function myFn() {
console.log(state); // RefImpl {_rawValue: {…}, _shallow: false, __v_isRef: true, _value: Proxy}
console.log(state.value); // Proxy {a: "a", f: {…}}
console.log(state.value.f); // Proxy {b: "b", t: {…}}
console.log(state.value.f.t); // Proxy {c: "c", s: {…}}
console.log(state.value.f.t.s); // Proxy {d: "d"}
// 界面不改变,因为shallowRef只监听第一层即state.value的变化
state.value.a = 1;
state.value.f.b = 2;
state.value.f.t.c = 3;
state.value.f.t.s.d = 4;
// 直接修改state.value,界面才会改变
state.value = {
a: '1',
f: {
b: '2',
t: {
c: '3',
s: {
d: '4'
}
}
}
}
}
return { state,myFn }
}
}
</script>
(3)triggerRef:指定监听某一层数据
<template>
<div>
<h1>{{state.a}}</h1>
<h1>{{state.f.b}}</h1>
<h1>{{state.f.t.c}}</h1>
<h1>{{state.f.t.s.d}}</h1>
<button @click="myFn">显示</button>
</div>
</template>
<script>
import { shallowRef, triggerRef } from 'vue';
export default {
name: 'App',
setup() {
let state = shallowRef({
a: 'a',
f: {
b: 'b',
t: {
c: 'c',
s: {
d: 'd'
}
}
}
});
function myFn() {
// 指定只监听某一层数据
state.value.f.t.s.d = 4;
triggerRef(state);
}
return { state,myFn }
}
}
</script>
5.模块分离
<template>
<div>
<form>
<input type="text" v-model="state2.stu.id">
<input type="text" v-model="state2.stu.name">
<input type="text" v-model="state2.stu.age">
<input type="button" value="添加" @click="addStu">
</form>
<ul>
<li v-for="(stu, index) in state.stus" :key="stu.id" @click="remStu(index)">{{stu.id}} - {{stu.name}} - {{stu.age}}</li>
</ul>
</div>
</template>
<script>
import { reactive } from 'vue';
export default {
name: 'HelloWorld',
setup() {
// 引入删除和添加模块
let { state, remStu } = useRemoveStudent();
let { state2, addStu } = useAddStudent(state);
return { state, remStu, state2, addStu }
}
}
// 删除模块
function useRemoveStudent() {
// 监听对象或数组需要使用reactive
let state = reactive({
stus: [
{id: 0, name: 'df0', age: 11},
{id: 1, name: 'df1', age: 14},
{id: 2, name: 'df2', age: 15},
]
})
// 点击移除该项
function remStu(index) {
state.stus = state.stus.filter((stu, idx) => idx != index);
}
return { state, remStu };
}
// 添加模块
function useAddStudent(state) {
let state2 = reactive({
stu: {
id: '',
name: '',
age:''
}
})
function addStu(e) {
e.preventDefault();
console.log(state2.stu);
const stu = Object.assign({}, state2.stu);
state.stus.push(stu);
}
return {state2, addStu}
}
</script>
6.toRaw获取原始数据
toRaw():获取原始数据,修改原始数据界面不会更新。
作用:ref/reactive数据每次修改都会被追踪,都会更新ui界面,非常消耗向性能。当一些操作不需要追踪时,可以通过toRaw()获取并修改原始数据。
获取reactive类型的原始数据:
<template>
<div>
<h1>{{state.name}}</h1>
<button @click="myFn">更改</button>
</div>
</template>
<script>
import {reactive, toRaw} from 'vue'
export default {
name: 'ToRaw',
setup() {
let state = reactive({
name: 'jds',
age: 14
});
let obj2 = toRaw(state);
function myFn() {
console.log(state); // Proxy {name: "jds", age: 14}
console.log(obj2); // {name: "jds", age: 14}
obj2.name = 'sds'; // 界面不更新
console.log(state); // Proxy {name: "sds", age: 14}
console.log(obj2); // {name: "sds", age: 14}
}
return { state, myFn }
}
}
</script>
获取ref类型的原始数据要注意加上.value:
let obj2 = toRaw(state.value);
7.markRaw永远不被追踪
即便使用reactive追踪也无反应
8.toRef
ref:将对象中的属性变成响应式的数据,修改响应式数据不会影响到原始数据
<template>
<div>
<h1>{{state}}</h1>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {ref} from 'vue';
export default {
setup() {
let obj = {name: 'fbdf'};
let state = ref(obj.name);
function myFn() {
console.log(obj); // {name: "fbdf"}
console.log(state); // RefImpl {_rawValue: "fbdf", _shallow: false, __v_isRef: true, _value: "fbdf"}
state.value = 'qwe'
console.log(obj); // {name: "fbdf"}
console.log(state); // RefImpl {_rawValue: "qwe", _shallow: false, __v_isRef: true, _value: "qwe"}
}
return {state, myFn}
}
}
</script>
toRef:将对象中的属性变为响应式数据,当修改响应式数据时,会影响原始数据,但ui不会更新。
<template>
<div>
<h1>{{state}}</h1>
<button @click="myFn">按钮</button>
</div>
</template>
<script>
import {toRef} from 'vue';
export default {
setup() {
let obj = {name: 'fbdf'};
let state = toRef(obj,'name');
function myFn() {
console.log(obj); // {name: "fbdf"}
console.log(state); // ObjectRefImpl {_object: {…}, _key: "name", __v_isRef: true}
state.value = 'qwe'
console.log(obj); // {name: "qwe"}
console.log(state); // ObjectRefImpl {_object: {…}, _key: "name", __v_isRef: true}
}
return {state, myFn}
}
}
</script>
ref 和toRef区别:
- ref->复制,修改响应式数据不会影响原始数据,数据发生改变,界面会更新
- toRef -> 引用,修改响应式数据会影响原始数据,数据发生改变,界面不会更新
toRef应用场景:想让响应式数据和原始数据关联起来,并且更新响应式数据之后还不想更新UI,就可使用。
9.toRefs
将对象中的所有属性变成响应式的数据,修改响应式数据会影响到原始数据。
let obj = {name: 'fhd', age: 12};
let state = toRefs(obj); //
// 相当于
let name = toRef(obj.name);
let age = toRef(obj.age);
10.生命周期