Vue3
前言
本文主要用于记录vue3的使用,参考视频【尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通】
Vue3
创建Vue3工程
两种方法 一种是传统的vuecli 另外一种是vite
使用vuecli创建该工程需要确保脚手架的版本在4.5以上
使用指令可以查看1
2
3vue -V
or
vue --version
否则重新安装1
npm install -g @vue/cli
创建1
vue create vue_test
启动1
2cd vue_test
npm run serve
先看一图流
分析工程解构
主要查看vue3和2有什么结构和写法上面的区别
从mainjs上面看 引入和实例化app的方法不一样了 而且也不能用vue2的形式写了1
2
3
4
5
6
7// 引入的不再是vue构造函数了 而是一个精简版的createApp工厂函数
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
vue3不需要根标签了 在vue2里面需要用div当做根标签编写
setup
setup是vue3使用组合api的地方 结合了之前2中的data methods等
setup是一个函数 里面可以直接定义变量 方法等
返回有两种情况 一种是返回定义的变量 此时模板中可以直接使用该变量1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<template>
<h1>这是a值{{ a }}</h1>
<h1>这是b值{{ b }}</h1>
</template>
<script>
export default {
name: "App",
setup() {
let a = 1;
let b = 2;
return {
a,
b,
};
},
};
</script>
返回的第二种情况是 返回一个渲染函数 该函数的html元素可以直接被渲染1
2
3 import { h } from "vue";
------------------------------------------------------------------------
return () => h("h1", "hahahah");
除此之外 data methods写法可以正常访问setup的数据 但是反过来不行 即vue2可以访问vue3 但3不能访问2 而且最好两者不要混用!
总结一图流
ref函数
ref函数可以将数据变成响应式的,还用于处理对象类型。如果不设置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<template>
<div>
<h1>学习ref</h1>
<h1>jojoname is {{jojo}}</h1>
<button @click="changeJOJO">点击改变jojo</button>
</div>
</template>
<script>
// 引入ref
import { h, ref } from "vue";
export default {
name: "App",
setup() {
// 将数据变成响应式
let jojo = ref("jotaro");
console.log(jojo);
function changeJOJO() {
// 改变数据的方式
jojo.value = 'jostar';
}
return {
jojo,
changeJOJO
}
},
};
</script>
注意的点是 初学使用setup容易忘记最后将模板需要的变量和方法返回出去
然后 数据变成响应式之后 修改数据需要xxx.value
的方式修改
最后是ref的数据 看上去像是做了数据代理之后的结果 这个RefImpl的意思是reference 和 implement的组合 全称是引用实现的实例,我们称之为引用对象
类似于单例的加工返回单例,将函数交给ref处理后返回响应式
- ref实现响应式也是通过getter和setter即
object.defineProperty
- 将getter和setter藏在原型对象中 更简洁了
总结一图流
reative函数 处理对象类型数据
vue3对不同类型的数据有不同类型的响应式处理,处理对象的时候用的是ref则是Proxy形式,本质上是求助了reactive函数。
reactive函数主要用于处理对象类型数据 并且它是深层次处理的。
用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
32
33
34
35
36<template>
<div>
<h1>学习ref</h1>
<h1>jojoname is {{jojo}}</h1>
<h1>jojo author is {{jojoHome.author}}</h1>
<button @click="changeJOJO">点击改变jojo</button>
</div>
</template>
<script>
// 引入ref
import { h, ref } from "vue";
export default {
name: "App",
setup() {
// 将数据变成响应式
let jojo = ref("jotaro");
let jojoHome = ref({
author:'huangmu',
jojoone:'幻影之血'
})
console.log(jojoHome);
function changeJOJO() {
// 改变数据的方式
jojo.value = 'jostar';
jojoHome.value.author = 'dio哒'
}
return {
jojo,
jojoHome,
changeJOJO
}
},
};
</script>
在上述ref的使用过程我们知道 用ref将对象变成响应式的 修改的时候需要对象点value点具体的属性
而reactive则不需要点value 而且可以检测到深度的数据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<template>
<div>
<h1>学习ref</h1>
<h1>jojoname is {{jojo}}</h1>
<h1>jojo author is {{jojoHome.author}}</h1>
<h1>find 套娃 is {{jojoHome.a.b.c}}</h1>
<button @click="changeJOJO">点击改变jojo</button>
</div>
</template>
<script>
// 引入ref
import { h, reactive, ref } from "vue";
export default {
name: "App",
setup() {
// 将数据变成响应式
let jojo = ref("jotaro");
let jojoHome = reactive({
author:'huangmu',
jojoone:'幻影之血',
a:{
b:{
c:'套娃呢?'
}
}
})
console.log(jojoHome);
function changeJOJO() {
// 改变数据的方式
jojo.value = 'jostar';
jojoHome.author = 'dio哒';
jojoHome.a.b.c = '对啊'
}
return {
jojo,
jojoHome,
changeJOJO
}
},
};
</script>
一图流
- reactive将源对象转化为代理对象
- 为什么基本类型数据最好用
ref
对象类型要用reactive
? - 首先ref都能处理这两者 但是在处理对象类型的时候借用了
reactive
,而reactive
只能处理对象类型,不能处理基本类型 ref
和reactive
处理对象类型有什么区别- 如果同样都是包裹了一个对象,比如说
obj
,那么访问的时候ref需要使用obj.value
才能访问到Proxy
下的obj
对象 而reactive直接使用obj
就可以访问。
vue3中的响应式原理
回顾vue2的响应式,我们用对对象类型的数据用object.defineProperty
对属性进行读取,修改和拦截。对数组类型的数据进行包裹然后使用原生的方法修改数组。
弊端:
- 不支持删除delete,以及新增(点操作符)
- 不能通过下标直接修改数组,需要用数组原生的几个方法才行
vue3中的响应式是通过Proxy代理,拦截
对比vue2的修改方法,有以下几点不同
- proxy的get和set接收了对象和属性值两个参数 通过键值对的方式修改
- proxy对于增加的操作包含在了set方法中,意思是set方法可以检测到增加
- proxy有了delete操作 返回值最好写删除的结果
上面是简略版
实际上底层不是这样实现的
就需要讲解到Reflect反射这个东西了,首先这个Reflect是可以和Object一样进行数据的属性增删改查操作
如:
但是又有点不同在于 如果重复写了Object.defineProperty
那么会报错 而Reflect
会返回一个布尔值 所以可以存在两个一样的reflect 但是只会执行第一个
最后
setup的两个注意点
setup在beforeCreate
之前调用,且this
的值是undefined
接收两个参数 一个是props
一个是context
对于props
,接收的是一个Proxy类型的的数据,如果没有和props
对应会有警告出现,如果没有传该值则是undefined
对于context
参数,有三个值接收
对于emit属性,就是触发事件,它可以使用context.emit
触发,用于父子通信
对于slot属性,插槽,需要注意和vue2不同的一点是 传递具名插槽的时候不再使用slot="xxx"
而是使用v-slot:xxx
这个是一个兼容性的问题
总结一图流:
computed计算属性
可以像vue2一样使用computed,但vue3里面使用可以import之后使用箭头函数的形式使用
注意 因为结合了reative函数,所以计算属性赋值可以是响应式对象的属性的形式 且最后不用返回
但是这样一来有一个问题出现 就是这个computed之后的属性是可读不可写的,这样就导致修改数据会出错,那么如何让他可读可写呢?
使用computed的全称写法 里面包裹的是一个对象
总结一图流:
watch监视属性
vue3中依然可以在setup外面写watch方法
在vue3自身中,watch方法可以写为如下形式
检测ref数据
注意 以上是检测ref数据 不是reactive的数据
- 不同于computed不需要接收值 所以不用变量收集
- 检测多个数据的时候采用数组的形式
- 如果输出new和old的值 会发现它们是用数组的形式存储,即,修改后存在数组里面显示
- 那么抛出疑问 vue2有immediate 那vue3应该怎么写?在watch里面写第三个属性即可
1
2
3
4
5setup(){
watch(sum,(newValue,oldValue)=>{
console.log(newValue,oldValue);
},{immediate:true})
} - 那么deep应该如何写?会出现什么问题?
在reactive生成的响应式数据自动开启了deep监视,导致deep配置项无效 且无法关闭 - 那么reative的数据是如何监视以及出现的问题?
如果直接写watch一个reactive的对象 那么会出现new和old值一样的问题
暂时没有办法解决这个问题
4 因为深度监视没办法关闭 开着会浪费性能的问题,所以如果想监视一个reative响应式对象中的一个数据 直接使用对象.属性
是不行的,vue3规定必须要返回这个值1
2
3watch(()=>person.age,(newValue,oldValue)=>{
console.log('xxx变化了',newValue,oldValue);
}) - 如果要监视reactive定义的一个对象中的某些属性看起来很麻烦= =
1
2
3watch([()=>person.age,()=>person.name],(newValue,oldValue)=>{
console.log('xxx变化了',newValue,oldValue);
}) - 特殊情况
- watch中的value问题
如果是一个ref定义的基本数据,那么取出来我们需要用到xx.value
而在watch里面想监视它 则不能写点value 因为watch监视的是一个结构 而不是一个具体的数据但是如果是ref定义的obj类型 即对象类型 那么用watch监视有两种方法1
2
3watch(sum,(newValue,oldValue)=>{
console.log('aaa',newValue,oldValue);
})
一是监视obj.value
,为什么呢?因为我们知道 ref定义的对象类型数据,直接输出是一个RefImplll即引用对象,它是再借助了reactive生成了proxy类型数据 即它的响应式来自reactive中的proxy 所以单单监视obj
是行不通的,必须监视obj.value
让他访问到响应式数据
二是开启deep配置项,从1我们知道,ref生成的对象类型数据必须监视到其value里面的proxy 那么换个角度来说只要他能深度检测到obj里面的value里面的proxy即可 所以开启这个深度监视也奏效
wacthEffect
watchEffect方法的功能用一句话来说就是用谁就监视谁
首先它区别于watch 不需要传入任何参数1
2
3
4
5watchEffect(()=>{
const x1 = sum;
const x2 = person.job.j1.salary;
console.log('回调执行')
})
刚刚说了 用到谁就去监视谁 所以在这里面 用到了sum 和person.job.j1.salary
这两个变量,前者好理解 就是一个基本的数据检测 但后者是深度的数据 结果是也能检测出来.说明这个方法非常的智能,可以深度检测.
总结一图流:
- computed和watchEffect很像的原因?
前者是有数据变化了,整个都要重新计算然后返回结果
后者也是一样 有东西变了 就执行回调 - 两者不一样的地方?
前者更注重的是回调的结果 所以要有一个返回值
而后者更注重过程 所以不用写返回值
vue3生命周期
- 在vue3里面 原先vue2中的
beforeDestroy和destroyed
变成了beforeUnmounted和unmounted
- 原先在vue2里面 如果创建了
new Vue
并且传入了配置项,但不写el 也不在vm中挂载el,意味着此时模板没有被解析 但是它经过了beforeCreate和created
两个钩子 这样的情况是不必要的,在vue3中得到了改善,vue3需要app挂载完后才能走下一步 - vue3也提供了组合api形式的生命周期钩子 和vue2中的对应如下
hook
其实本质上就是js的模块化
定义一个hook文件夹 将需要的函数放进去并暴露方法最后return
和mixins有点类似
toRef
解决响应式丢失的问题
如果我们return变量的时候想调用的时候简写,比如:1
2
3
4return {
name:person.name,
age:person.age
}
这个时候数据虽然能用 但因为只是把响应式的数据赋值给了变量,而变量不是响应式的 所以就丢失了响应式.
使用toRef
或者toRefs
方法 即可把数据变成响应式的,从而return的变量也是响应式的
并且非常需要注意的是 toRef之后修改的数据 对应原先的数据也会修改!!!.1
2const name = toRef(person,'name');
console.log(name)//输出的是一个RefImpl对象
注意 对于深层次的数据变成响应式需要这样写1
const salary = toRef(person.job.j1,'salary');
如果需要抛出的变量很多 那么建议使用toRefs
1
2
3
4
5
6
7const x = toRefs(person);
//这样就抛出了person的全部属性了 且属性里面的数据也是Proxy的
//或者
return {
...toRefs(person)
}
//利用...运算符将person全部展开 但是需要注意的是 多层级的数据依旧需要在模板中一层一层写 比如job.j1.salary 暴露出来的只有job
- 为什么不在return的时候用ref包装呢?不是比toRef写法更简单吗?
结果上是可以的 但是这样会有个很严重的问题 ref只是将传过来的数据进行响应式 如果当前的数据改变页面也会变动 但是!!原先的数据并没有改变 改变的是ref新建的对象里面的数据
shallowReactive和shallowRef
shallowReactive
只考虑第一层的数据,意味着只对第一层做响应式,比较节能。shallowRef
如果是基本类型 那么和ref
没有区别 但是如果是对象类型 shallowRef
不会使其变成响应式
readonly和shallowReadonly
readonly
可以使得数据变成只读,虽然它是响应式的但是不允许更改
引申出页面没有变化的两种情况
- 数据不是响应式的 vue就检测不到
- 数据不可以被修改
readonly
属于这种情况shallowReadonly
让数据的第一层不能被修改 但深层次可以被修改
toRaw还原对象
toRaw
用于还原对象或者变量的响应式。
只能处理reactive定义的响应式对象markRaw
用之前讲讲不用它的时候的需求
如果已经将对象变成响应式并且暴露出去了,那么后面有个function想在刚刚的对象中添加新数据进去是不行的,因为toRefs
只把第一层的数据暴露了出去,新添加的数据是没有暴露的。解决方法有两种
- 给原先的对象赋一个空的新数据,这样就会把空数据暴露出去且是响应式,所以后面增加也会变动
- 将对象重新交出去(交了两次 一次是源对象 一次是
toRefs
)这样一来就是把整个响应式对象交了出去,既然是响应式的对象,那么也能检测到添加和删除的元素。
那么markRaw
的业务是什么呢?
上面说了新添加的响应式数据 如果这个数据特别庞大,层次特别丰富,只是展示到页面上,且不用变动数据的时候,那么这时候这个数据最好就不用响应式。这样一来就需要用到markRaw
标记这个数据,这样一来不管这个数据是进入到了响应式对象里,还是一开始给了它响应式,最后它都不会是响应式的。
customRef
创建一个自定义的ref 一般用于防抖的业务场景
代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20setup(){
function myRef(value,delay){
let timer;
return customRef((track,trigger)=>{
return{
get(){
track();//告诉vue这个值需要被追踪
},
set(newValue){
clearTimeout(timer);
timer = setTimeout(()=>{
value = newValue;
trigger();//告诉vue去更新页面
},delay)
}
}
})
}
let keyword = myRef('hello',500)
}
provide和inject
提供了祖孙通信的方式
响应式数据的判断
composition API的优势
讲到组合api,那么就要讲到这个option api了。后者是vue2的,和它的中文含义一样,是将数据和方法进行一项一项的配置。但是这样存在一定的劣势,比如当变量很多的时候,数据就不好维护了。如下图,绿色的数据对应绿色的功能,但它们的位置不一样 很难维护。
而到了vue3,借助了hook 就不会造成这样的问题