vue3.0

结合李江南老师的视频技术胖的视频和文档进行二次编辑的

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是如何变快的

diff算法优化: https://vue-next-template-explorer.netlify.app/

  • Vue2中的虚拟dom是进行全量的对比
  • Vue3新增了非静态标记(PatchFlag)
    1. 在与上次虚拟节点进行对比时候,只对比带有patch flag的节点
    2. 并且可以通过flag的信息得知当前节点要对比的具体内容
1
2
3
4
// template内的内容
<div>Hello World!</div>
<h1 :title='haha' :value='hh'>{{msg}}</h1>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 源码
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("div", null, "Hello World!"),
_createVNode("h1", {
title: _ctx.haha,
value: _ctx.hh
}, _toDisplayString(_ctx.msg), 9 /* TEXT, PROPS */, ["title", "value"])
], 64 /* STABLE_FRAGMENT */))
}
/*
不需要遍历节点一一对比
对动态的组件进行标记(只比较标记的内容)
数字的不同表示了 比较的内容不同
不需要针对整个节点去改变,只需要针对特定的内容去重新渲染

9 ---> 代表他动态的内容为 text 和 props
改变的时候只需要改变文本内容和属性
*/

hoistStatic 静态提升

  • Vue2中无论元素是否参与更新, 每次都会重新创建, 然后再渲染
  • Vue3中对于不参与更新的元素, 会做静态提升, 只会被创建一次, 在渲染时直接复用即可
1
2
3
4
// template内的内容
<div>Hello World!</div>
<h1 :title='haha' :value='hh'>{{msg}}</h1>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 源码
// 静态提升之前
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("div", null, "Hello World!"),
_createVNode("h1", {
title: _ctx.haha,
value: _ctx.hh
}, _toDisplayString(_ctx.msg), 9 /* TEXT, PROPS */, ["title", "value"])
], 64 /* STABLE_FRAGMENT */))
}

// 静态提升之后
const _hoisted_1 = /*#__PURE__*/_createVNode("div", null, "Hello World!", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_hoisted_1,
_createVNode("h1", {
title: _ctx.haha,
value: _ctx.hh
}, _toDisplayString(_ctx.msg), 9 /* TEXT, PROPS */, ["title", "value"])
], 64 /* STABLE_FRAGMENT */))
}

/*
每次render的时候全部内容会重新创建一次,会造成额外的内存消耗
通过把静态的节点提取为全局变量
所以就只需要创建一次
组件渲染的时候只需要调用就行了
*/

cacheHandlers 事件侦听器缓存

  • 默认情况下onClick会被视为动态绑定, 所以每次都会去追踪它的变化
    但是因为是同一个函数,所以没有追踪变化, 直接缓存起来复用即可
1
<button @click='handleClick'>按钮</button>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 开启事件侦听之前
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("button", { onClick: _ctx.handleClick }, "按钮", 8 /* PROPS */, ["onClick"]))
}

// 开启事件侦听之后
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("button", {
onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.handleClick(...args)))
}, "按钮"))
}
/*
开启之前,通过静态标记的形式来实现 ==> 比较属性(但是方法是不变的)
开启之后,不使用静态标记,所以不会进行比较。通过一个变量对方法进行缓存,下次在调用的时候直接从缓存中获取,不去再次创建整个函数
*/

ssr渲染

  • 当有大量静态的内容时候,这些内容会被当做纯字符串推进一个buffer里面,即使存在动态的绑定,会通过模板插值嵌入进去。这样会比通过虚拟dmo来渲染的快上很多很多。

  • 当静态内容大到一定量级时候,会用_createStaticVNode方法在客户端去生成一个static node,这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染。

组合API(composition api)

vue2.x实现列表的增删功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*
实现一个功能就需要
在data内定义数据,
在methods / computed / watch 中实现业务逻辑来操作数据
造成 数据和业务逻辑 分散
*/
<template>
<div>
<form>
<input placeholder="id" type="text" v-model="info.id" />
<br>
<input placeholder="name" type="text" v-model="info.name" />
<br>
<input type="submit" @click="handleAdd" />
</form>
<ul>
<li
v-for="(item,index) in list"
:key="item.id"
@click="handleRemove(index)"
>{{item.name}} ----- {{ index }}</li>
</ul>
</div>
</template>

<script>
export default {
name: "App",
data() {
return {
info: {
id:'',
name:''
},
list: [
{ id: 0, name: "jjj" },
{ id: 1, name: "hhh" },
{ id: 2, name: "0hh" },
],
itemRefs: [],
};
},
methods: {
handleRemove(index) {
this.list.splice(index, 1);
},
handleAdd(e){
e.preventDefault();
console.log(this.info)
this.list.push(this.info)
this.info = {
id:'',
name:''
}
}
},
};
</script>

vue3组合API的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<template>
<div>
<h1>{{count}}</h1>
<button @click="handleAdd">添加</button>
</div>
</template>

<script>
// 只能监听简单类型的变化,不能监听复杂类型的变化
import { ref } from "vue";

// 监听复杂类型的变化
import { reactive } form "vue";

export default {
name: "App",

// setup为组合函数的入口函数
setup() {
// 定义变量和初始化变量 count是一个对象,value是他的值
let count = ref(0);

// 定义方法
function handleAdd(){
count.value ++
}

// 暴露变量和方法以供使用
return { count,handleAdd }
},
};

// 可以将setup里面的内容单独抽离出来

export default {
name: "App",
setup() {
let { obj, handleRemove } = useRemove();
return { obj, handleRemove };
}
}

function useRemove() {
let obj = reactive({
arr: [
{ id: "1", name: "dorren" },
{ id: "2", name: "jerry" },
],
});
function handleRemove(index) {
obj.arr.splice(index, 1);
}
return { obj, handleRemove };
}
// 使用的时候通过obj.arr使用


// 抽离方式2
import { time, handleChange } from "./hooks/useGetTime.ts";
export default {
setup() {
return { time, handleChange };
}
}

// ./hooks/useGetTime.ts
import { ref } from 'vue'
let time = ref("00:00:00");
let handleChange = () => {
let date = new Date();
const hour =
date.getHours() < 10 ? "0" + date.getHours() : date.getHours();
const min =
date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes();
const sec =
date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
time.value = hour + ":" + min + ":" + sec;
setTimeout(() => {
handleChange()
}, 1000);
};
export { time, handleChange }

组合API完整示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

<div>
<form>
<input placeholder="id" type="text" v-model="info.info.id" />
<br />
<input placeholder="name" type="text" v-model="info.info.name" />
<br />
<button @click="handleAdd">添加</button>
</form>
<h1
v-for="(item,index) in obj.arr"
:key="item.id"
@click="handleRemove(index)"
>{{item.name}}---{{item.id}}</h1>
</div>

<script>
import { reactive } from "vue";

export default {
name: "App",
setup() {
let { obj, handleRemove } = useRemove();
// 参数传递
let { info, handleAdd } = useAdd(obj);

return { obj, handleRemove, info, handleAdd };
},
};

function useRemove() {
let obj = reactive({
arr: [
{ id: "1", name: "dorren" },
{ id: "2", name: "jerry" },
],
});

function handleRemove(index) {
obj.arr.splice(index, 1);
}
return { obj, handleRemove };
}

function useAdd(obj) {
let info = reactive({
info: {
id: "",
name: "",
},
});

function handleAdd(e) {
e.preventDefault();
const item = Object.assign({}, info.info);
obj.arr.push(item);
info.info.id = "";
info.info.name = "";
}
return { info, handleAdd };
}
</script>

组合API内使用生命周期钩子

选项API Hook inside setup
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmountvue2的beforeDestroy
unmounted onUnmounted
errorCaptured onErrorCaptured捕获子组件异常
renderTracked onRenderTracked对变化数据进行跟踪
renderTriggered onRenderTriggered精确跟踪触发(有新旧值))

reactive

  • vue3提供的实现响应式数据的方法
  • 用es6的proxy取代了defineProperty来实现响应式

tips:

  • reactive参数必须是对象(json/arr)
  • 如果给reactive传递了其他类型
    • 默认情况下修改值,界面不会自动更新
    • 如果想更新,可以通过重新赋值的方式

ref

  • vue3提供的对简单值的监听
  • 本质是reactive ref(xx) —> reactive({ value: xx })

tips:

  • 在vue中使用ref的值不用通过value获取
  • 在js中使用ref的值必须通过value获取 msg.value = 111

isRef和isReactive

用于判断变量类型

1
2
3
4
5
6
7
import { isRef,isReactive } from 'vue'

setup() {
let num = ref(99)
console.log( isRef(num) ) // true
console.log( isReactive(num) ) // false
}

组件通信

父传子( props )

基本和vue2.x一样,父组件绑定属性,子组件通过props获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 父组件
<child-component :name="name" :age="count" />

// 子组件
export default{
props:{
name:{
type:String, // 类型 String、Number、Boolean、Array、Object、Date、Function、Symbol
required:true, // 是否必传 true、false
default:'jerry', // 默认值 对象或数组默认值必须从一个工厂函数获取 ()=>...

// 自定义验证函数
validator:(value:string)=> return ['sherry','jerry','doreen'].indexOf(value)!==-1
},
age:{
// ...
}
},

// 通过参数、在setup中使用
setup:(props)=>{
// props是通过ref包裹的 响应式引用
console.log(props.name)
}
}

子传父( emit )

有细微的差别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 子组件
<button @click='handleEmit'> 按钮 </button>

setup:(props,context)=>{
// context 不是响应式的 可写为 { attrs,slots,emit } Attribute,插槽,广播
function handleEmit(){
context.emit('toParentComponent','some info')
}
return { handleEmit }
}

// 父组件
<child-component @toParentComponent='hanldeFun' />

setup:()=>{
function handleFun(value:string){
console.log(value) // some info
}
return { handleFun }
}

父传孙子( provide inject )

父组件通过provide传递,孙子组件通过inject接收

1
2
3
4
5
6
7
8
9
 // 父组件 传递
setup: () => {
provide("name", "value");
}

// 子组件 接收
setup: (props, context) => {
const result = inject("name");
},

递归监听

无论是通过ref还是reactive创建的数据都是递归监听
能够监听每一层数据 并且把每一层数据都包装成proxy对象

非递归监听

shallowReactiveshallowRef

  • 只为某个对象的私有(第一层)属性创建浅层的响应式代理,不会对“属性的属性”做深层次、递归地响应式代理,而只是保留原样。
  • 只有第一层被封装成proxy对象,其余内容都是对象的形式存储,如果第一层内容没有被改变,就不会触发页面的更新

Tips:
如果是通过shallowRef创建的数据,vue监听的是.value的变化,并不是第一层的变化。

1
2
3
4
5
6
7
8
<template>
<div>
<h1>{{date.data}}</h1>
<h1>{{date.obj.name}}</h1>
<h1>{{date.obj.son.name}}</h1>
<button @click="handleAdd">添加</button>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { shallowReactive } from "vue";
export default {
name: "App",
data() {
return {};
},
setup() {
let date = shallowReactive({
date: 'hhh',
obj: {
name:'doreen',
son: {
name: 'jerry'
}
}
});
function handleAdd() {
// 第一层数据改变,点击事件后,页面数据更新
date.data = 'eee'
date.obj.name = 'sherry'
date.obj.son.name = 'kris'

// 第一层数据没有改变,点击事件后,数据改变页面没有更新
date.obj.name = 'sherry'
date.obj.son.name = 'kris'

}

return { date, handleAdd };
},
};

triggerRef

针对ref类型的数据,vue3提供了triggerRef的方法进行触发更新

(对于reactive类型的数据没有提供相应方法)

1
2
3
4
5
6
7
8
import { triggerRef } from 'vue'
...

date.value.son.name = 'kris'
triggerRef(date);

...

toRow

获取ref / reactive 的原始数据

ref / reactive 数据类型的特点:
每次修改都会被追踪,触发界面更新,但是会非常消耗性能
对于一些不需要追踪的操作,不需要更新界面,这个时候
就可以用toRow方法拿到他的原始数据,对原始数据进行修改
这样就不会被追踪并且不会更新界面,提高了性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { reactive,toRow } from "vue";
export default {
setup() {
let obj = { name: "doreen",sex: "女" };
let state = reactive(obj);
let obj2 = toRaw(state); // 获取ref / reactive 的原始数据
// let state = ref(obj);
// let obj2 = toRow(state.value) // ref需要通过.value获取原始数据


// console.log(obj === obj2); // true
// console.log( obj === state ) false
/*
state 和 obj :
引用关系,state本质是一个proxy对象,在这个proxy对象中引用了obj
*/
function handleChange() {
/*
直接修改 obj 无法触发更新
通过proxy对象包装后,才能触发页面更新
*/
obj.name = "sherry";
console.log(obj); // { name:'sherry',sex:'女' }
console.log(state); // { name:'sherry',sex:'女' }
}

return { obj, state, handleChange };
},
};

markRow

让数据永远不会被追踪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
setup() {
let obj = markRaw({
name: "doreen",
sex: "女",
});
let state = ref(obj);

function handleChange() {
// 不触发页面更新
obj.name = "sherry";
console.log(obj); // { name:'sherry',sex:'女' }
console.log(state); // { name:'sherry',sex:'女' }
}

return { state, handleChange };
},

toRef

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
setup() {
let obj = {
name: "doreen",
};
/**
* let state = ref(obj.name)
* => let state = ref('doreen')
* => let state = reactive({ value: 'doreen' })
*/
let state = ref(obj.name);

function handleChange() {
state.value = "sherry";
/**
* 如果利用ref将某一个对象中的属性变成响应式的数据
* 我们修改的响应式数据不会影响到原始数据
*/
console.log(obj); // { name:'doreen' }
console.log(state); // { value: 'sherry' }
}

return { state, handleChange };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
setup() {
let obj = {
name: "doreen",
};
/*
ref和toRef的区别:
ref -> 复制,修改响应式数据不会影响以前的数据
数据发生改变,界面就会自动更新

toRef -> 引用,修改响应式数据会影响以前的数据
数据发生改变,界面也不会自动更新
*/
let state = toRef(obj,'name');
console.log(state)
function handleChange() {
state.value = "sherry";
/**
* 如果利用toRef将某一个对象中的属性变成响应式的数据
* 我们修改的响应式数据会影响到原始数据
* 如果响应式的数据通过toRef创建的,修改数据并不会触发界面更新
*/
console.log(obj); // { name:'sherry' }
console.log(state); // { value: 'sherry' }
}

return { state, handleChange };
},

customRef

返回一个ref对象,可以显式的控制依赖追踪和触发响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { customRef } from "vue";

// 自定义 ref
function myRef(value) {
// track -> 追踪 trigger -> 触发
// 没有这两个参数,ui界面就不会被触发更新
return customRef((track,trigger) => {
return {
get() {
track() // 告诉vue 数据需要被追踪变化的
console.log("get:", value);
return value;
},
set(newV) {
console.log("set:", newV);
value = newV
trigger() // 触发更新
},
};
});
}
export default {
setup() {
let state = myRef(15);
function handleChange() {
state.value++;
}
return { state, handleChange };
},
};

应用场景:
setup中不能进行异步操作
如果需要通过网络请求对数据进行赋值
就可以使用customRef来获取请求数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 import { customRef } from "vue";
function myRef(value) {
// track -> 追踪 trigger -> 触发
return customRef((track, trigger) => {
// 在get之前对数据进行赋值 不会造成重复刷新
fetch(value)
.then((res) => {
return JSON.json();
})
.then((data) => {
console.log(data);
value = data;
trigger(); // 获取数据、刷新页面
})
.catch((err) => {
console.log(err);
});

return {
get() {
track(); // 数据需要被追踪变化的
console.log("get:", value);
return value;
},
set(newV) {
console.log("set:", newV);
value = newV;
trigger(); // 触发更新
},
};
});

}
export default {
setup() {
let state = myRef('../public/data.json');
return { state };
},
};

获取元素

vue2x中通过绑定ref属性,然后通过$refs.xxx来获取元素
vue3取消了$refs,但是也可以通过ref来获取元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

<h1 ref="box"></h1>


import { ref, onMounted } from "vue";
export default {
setup() {
let box = ref(null);

onMounted(() => {
console.log(box.value); // <h1></h1>
});
// setup的生命周期在beforeCreated之前
console.log(box.value) // null
return { box };
},
};

readonly

用于创建一个只读的数据,并且递归只读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { readonly } from "vue";
export default {
setup() {
let box = readonly({
name: "doreen",
attr: {
age: 12,
},
});

function handleChange() {
box.name = "sherry";
box.attr.age = 22;

console.log(box);
/**
* {
name: "doreen",
attr: {
age: 12,
},
}
*/
}
return { box, handleChange };
},
};

shallowReadonly

创建一个第一层只读的数据 (非递归)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { shallowReadonly } from "vue";
export default {
setup() {
let box = shallowReadonly({
name: "doreen",
attr: {
age: 12,
},
});

function handleChange() {
box.name = "sherry";
box.attr.age = 22;

console.log(box);
/**
* {
name: "doreen",
attr: {
age: 22,
},
}
*/
}
return { box, handleChange };
},
};

isReadonly

判断是否是readonly类型 返回布尔值

1
isReadonly(state) // true / false

computed

使用getter函数,从getter返回的值返回一个不变的响应式ref对象

1
2
3
4
5
6
7

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2
plusOne.value++ // error

通过getset函数对象创建ref对象

1
2
3
4
5
6
7
8
9
10
11
12

let msg = ref("title");
let str = computed({
get: () => msg.value + " get ",
set: (val) => {
msg.value = val + " set ";
},
});

console.log(str.value); // title get
str.value = "set value"; // set value

watch

1
2
3
4
5
6
7
8
9
10
import { watch } from 'vue'

// ...
// 多个观察对象 watch([val1,val2 ... ],([])=>{ ... })
watch(msg, (newV, oldV) => {
console.log(newV, oldV);
});

// ...

Teleport组件

vue项目在编译后所有的内容都是在 <div id='app'></div> 标签下进行的,样式什么的会受到全局样式的影响

Teleport是一种能够将我们的模板移动到 DOM 中 Vue app 之外的其他位置的技术

属性

to

<teleport to="#some-id / .some-class / [data-teleport]" /> to属性后面必须是有效的查询选择器或 HTMLElement (如果在浏览器环境中使用)。目的是将teleport内的元素移动到该容器中

disabled

<teleport to='...' :disabled='disabledFlag'>此可选属性可用于禁用 <teleport> 的功能,这意味着其插槽内容将不会移动到任何位置,而是在您在周围父组件中指定了 <teleport> 的位置渲染

使用

  1. public - index.html
    <div id="app"></div> 下方或者什么地方新增 <div id="example"></div>

  2. 新建一个 Example.vue 文件 并挂载到 App.vue 中进行展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- Example.vue -->
<template>
<teleport to='#example'>
<div> ... </div>
</teleport>
</template>
<style>
/* ... */
</style>


<!-- App.vue -->
<template>
<div>
....
<example/>
</div>
</template>

这样Example.vue所写的内容就存在于<div id="example"></div>标签中,而不是<div id="app"></div>中,样式不会受app的影响

Suspense异步组件

Suspense组件用于在等待某个异步组件解析时显示后备内容

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

<!-- Example.vue -->
<template>
<div> {{result}} </div>
</template>
<script lang='ts'>
...
export default defineComponent({
async setup(){
const rawData = await axios.get('...');
return { result:rowData.data }
}
})

</script>
<style>
/* ... */
</style>


<!-- App.vue -->
<template>
<Suspense>
<template #default>
<!-- 请求成功后展示的组件 -->
<example/>
</template>
<template #fallback>
<!-- 没请求的时候展示的组件 -->
<h1>Loading...</h1>
</template>
</Suspense>
</template>
<script lang='ts'>
// 捕获请求异常
import { onErrorCaptured } from 'vue'
export default{
...
setup(){
// 返回bool值
onErrorCaptured((error)=>{
console.log(error)
return true
})
}
}
</script>

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2019-2023 John Doe
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信