banner
Hi my new friend!

vue3内容

Scroll down

vue3简介

2020年9月18日,Vue.js发布3.0版本,代号:One Piece(海贼王)
耗时2年多、2600+次提交、30+个RFC、600+次PR、99位贡献者
github上的tags地址:https://github.com/vuejs/vue-next/releases/tag/v3.0.0

vue3带来了什么

1.性能的提升
打包大小减少41%

初次渲染快55%, 更新渲染快133%

内存减少54%

2.源码的升级
使用Proxy代替defineProperty实现响应式

重写虚拟DOM的实现和Tree-Shaking

3.拥抱TypeScript
Vue3可以更好的支持TypeScript

vue3新的特性

Composition API(组合API)

setup配置
ref与reactive
watch与watchEffect
provide与inject

新的内置组件

Fragment
Teleport
Suspense
其他改变

新的生命周期钩子
data 选项应始终被声明为一个函数
移除keyCode支持作为 v-on 的修饰符
……

创建vue3项目

1
2
3
4
5
6
7
8
9
 ## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue3_study
## 启动
cd vue3_study
npm run serve

vue2 Options API的弊端

在Vue2中,我们编写组件的方式是 OptionsAPI:

Options API的一大特点就是在对应的属性中编写对应的功能模块;

比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变,也包括生命 周期钩子;
但是这种代码有一个很大的弊端:

当我们实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中;

当我们组件变得更大、更复杂时,逻辑关注点的列表就会增长,那么同一个功能的逻辑就会被拆分的很分散;

尤其对于那些一开始没有编写这些组件的人来说,这个组件的代码是难以阅读和理解的(阅读组件的其他人);

setup函数

我们先来研究一个setup函数的参数,它主要有两个参数:

第一个参数:props

第二个参数:context

props非常好理解,它其实就是父组件传递过来的属性会被放到props对象中,我们在setup中如果需要使用,那么就可以直接通过props参数获取:

对于定义props的类型,我们还是和之前的规则是一样的,在props选项中定义;

并且在template中依然是可以正常去使用props中的属性,比如message;

如果我们在setup函数中想要使用props,那么不可以通过 this 去获取(后面我会讲到为什么);

因为props有直接作为参数传递到setup函数中,所以我们可以直接通过参数来使用即可;

另外一个参数是context,我们也称之为是一个SetupContext,它里面包含三个属性:

attrs:所有的非prop的attribute;

slots:父组件传递过来的插槽(这个在以渲染函数返回时会有作用,后面会讲到);

emit:当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件);

setup函数的返回值
setup函数的两种返回值:

若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
若返回一个渲染函数:则可以自定义渲染内容

reactive函数

作用: 定义一个对象类型的响应式数据(基本类型不要用它,控制台会报警告,要用ref函数)
语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
reactive定义的响应式数据是“深层次的”。
内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。

ref函数

作用: 定义一个响应式的数据
语法: const xxx = ref(initValue)
创建一个包含响应式数据的引用对象(reference对象,简称ref对象), 其内部的值是在ref.value属性中被维护的 。
JS中操作数据: xxx.value
模板中读取数据: vue自动帮我们进行解包操作,不需要.value,直接:


备注:
接收的数据可以是:基本类型、也可以是对象类型。
基本类型的数据:响应式依然是靠Object.defineProperty()的get与set完成的。
对象类型的数据:内部 求助 了Vue3.0中的一个新函数—— reactive函数。

toRefs

如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值,那么之后无论是修改解构后的变量,还是修改reactive返回的state对象,数据都不再是响应式的:Vue为我们提供了一个toRefs的函数,可以将reactive返回的对象中的属性都转成ref;

1
const {name,age} = toRefs(state)

那么我们再次进行结构出来的 name 和 age 本身都是 ref的;
这种做法相当于已经在state.name和ref.value之间建立了 链接,任何一个修改都会引起另外一个变化;

toRef

1
2
3
 const name= toRef(state, 'name ' );const {age} = state;
const changeName = ()=>state.name = "coderwhy";

如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法
应用: 要将响应式对象中的某个属性单独提供给外部使用时。

ref 的其他API

unref
如果我们想要获取一个ref引用中的value,那么也可以通过unref方法:

如果参数是一个 ref,则返回内部值,否则返回参数本身;

这是 val = isRef(val) ? val.value : val 的语法糖函数;

isRef
判断值是否是一个ref对象。

shallowRef
创建一个浅层的ref对象;

triggerRef
手动触发和 shallowRef 相关联的副作用
shallowRef
shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。

什么时候使用?

如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
如果有一个对象数据,后续功能不会修改该对象中的属性,而是产生新的对象来替换 ===> shallowRef。
customRef
创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制:

它需要一个工厂函数,该函数接受 track 和 trigger 函数作为参数;

并且应该返回一个带有 get 和 set 的对象;

ref & reactive

从定义数据角度对比:
ref用来定义:基本类型数据。
reactive用来定义:对象(或数组)类型数据。
备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象。
从原理角度对比:
ref通过Object.defineProperty()的get与set来实现响应式(数据劫持)。
reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
从使用角度对比:
ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value。
reactive定义的数据:操作数据与读取数据:均不需要.value。

响应式原理

vu2

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
 // vue2.x 双向数据绑定原理是通过Objcet.defineProperty来实现的
// 这种方式有缺点: 数组的长度 数组的内容发生变化检测不到

let obj = {
name: 'zs',
age: 18,
phone: {
name: 'iphone'
}
}
// let obj = [1, 2, 3, 4, 5]
// 视图更新的方法
function render() {
console.log('视图更新了 ~');
}

let methods = ['push', 'pop', 'shift', 'unshift', 'sort', 'reverse', 'splice']
// 将数组的原型保存一份
let arrProto = Array.prototype
// 创建原型 将数组原型拷贝一份
let proto = Object.create(arrProto)
// 重写数组的方法
methods.forEach(method => {
proto[method] = function () { // AOP 面向切面编程
// 改变了数组的数据
arrProto[method].call(this, ...arguments)
// 重新渲染视图
render()
}
})

// 观察者模式
function observe(obj) {
// 判断一个对象是不是数组
// if(Object.prototype.toString.call(obj) === '[object Array]'){
if (Array.isArray(obj)) {
// 让观测的对象的原型和我们自己重写的原型建立关系
obj.__proto__ = proto
return;
}
// 如果观察的是一个对象的话 对其属性进行响应式监听(set、get)
if (Object.prototype.toString.call(obj) === '[object Object]') {
// 取出对象中的每一个键和值
for (let key in obj) {
// 调用响应式处理函数
defineReactive(obj, key, obj[key])
}
}
}
// 观察obj对象
observe(obj)

// 响应式处理
// Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
function defineReactive(obj, key, val) {
// 检测对象的属性值是否还是对象
observe(val)
Object.defineProperty(obj, key, {
// 获取
get() {
return val
},
// 设置
set(newVal) {
// 检测设置的值是不是对象
observe(newVal)
// 新值和旧值不相等的时候
if (newVal !== val) {
// 覆盖掉原来的val值
val = newVal
// 通知render函数重新渲染视图
render()
}
}
})
}

// obj.name = 'lisi'
// obj.age = 20
// console.log(obj.name, obj.age);
// 至此 基本可以实现数据发生变化 视图更新的效果
// 但是 如果数据有多层呢 也就是属性对应的值也是对象?
// obj.phone.name = 'huawei'
// console.log(obj.phone.name);

// 修改的值和原来的值一样 不需要重新渲染视图
// obj.phone.name = 'iphone'

// 如果重新为对象obj的phone赋值一个新的对象呢? 视图要重新渲染
// obj.phone = {name: 'huawei'}

// 并且当修改新的对象的属性值时 视图也要重新渲染
// obj.phone.name = 'zs'

// 为对象新增属性值呢? 也是没有办法监测到的
// obj.sex = 'man'
// $set来解决这个
function $myset(obj, key, val){
if(Array.isArray(obj)){
return obj.splice(key, val)
}
defineReactive(obj, key, val)
}
// $myset(obj, 'sex', 'man')
// 修改新增的属性值 视图也能更新
// obj.sex = 'woman'

// obj.phone = Object.assign(obj.phone, {price: '666'})
// obj.phone.price = 888

// obj.phone = {...obj.phone, ...{price: '666'}}
// obj.phone.price = '888'
// 以后vue涉及到给data中的对象新增属性时 有三种方式
// 1、this.$set() || Vue.set()
// 2、obj = Object.assign(原对象, 新对象(新增的属性:值))
// 2、obj = {...原对象, ...新对象(新增的属性:值)}

// =================== 数组 ======================
// 数组的响应式数据处理依赖的并不是Object.defineProperty 而是对数组的能够引起数据变化的方法进行重写
// obj.push(6)
// obj.length ++
// obj[0] = 88
// $myset(obj, '0', 66)

vue3

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
 // vue3响应式原理利用了es6提供的proxy Api
// 可以直接监听到对象和数组内容及长度的变化
// 缺点: 兼容性差 支持就用proxy 不支持就用Objcet.defineProperty
let obj = {
name: 'zs',
girlFriend: { name: '刘亦菲' },
arr: [1, 2, 3]
}

function render() {
console.log('视图重新渲染 ~');
}

let handler = {
get(target, key) {
// Reflect.get(target, propertyKey[, receiver])
// 获取对象身上某个属性的值,类似于 target[name]。
// 如果属性的值是一个对象的话 则重新进行代理 设置set&get
if(typeof target[key] == 'object' && target[key] != null){
return new Proxy(target[key], handler)
}
return Reflect.get(target, key)
},
set(target, key, val) {
// if(target[key] == 'length') return true
Reflect.set(target, key, val)
render()
// return true表示赋值成功 如果不返回true 在严格模式下可能会报TypeError
return true
}
}

let proxy = new Proxy(obj, handler)
// 使用代理后的对象
// console.log(proxy.name);
// console.log(proxy.grilFriend);

// proxy.name = 'lisi'
// 修改深层次的对象的属性 无法监听到
// proxy.grilFriend.name = '黄圣依'
// console.log(proxy.name);
// console.log(proxy.grilFriend);
// console.log(obj.name);
// console.log(obj.grilFriend);

proxy.arr[0] = '11'
proxy.arr.length ++
// console.log(proxy.arr.length);
console.log(obj.arr.length);

觉得写的不错 就可怜可怜博主吧.

其他文章
cover
小程序
  • 21/10/25
  • 10:46
  • 1.8k
  • 6