前言

本文主要用于记录vue3的使用,参考视频【尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通】

Vue3


创建Vue3工程

两种方法 一种是传统的vuecli 另外一种是vite

使用vuecli创建该工程需要确保脚手架的版本在4.5以上
使用指令可以查看

1
2
3
vue -V
or
vue --version

否则重新安装
1
npm install -g @vue/cli

创建
1
vue create vue_test

启动
1
2
cd 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只能处理对象类型,不能处理基本类型
  • refreactive处理对象类型有什么区别
  • 如果同样都是包裹了一个对象,比如说obj,那么访问的时候ref需要使用obj.value才能访问到Proxy下的obj对象 而reactive直接使用obj就可以访问。

vue3中的响应式原理

回顾vue2的响应式,我们用对对象类型的数据用object.defineProperty对属性进行读取,修改和拦截。对数组类型的数据进行包裹然后使用原生的方法修改数组。
弊端:

  1. 不支持删除delete,以及新增(点操作符)
  2. 不能通过下标直接修改数组,需要用数组原生的几个方法才行

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的值 会发现它们是用数组的形式存储,即,修改后存在数组里面显示
  1. 那么抛出疑问 vue2有immediate 那vue3应该怎么写?
    1
    2
    3
    4
    5
    setup(){
    watch(sum,(newValue,oldValue)=>{
    console.log(newValue,oldValue);
    },{immediate:true})
    }
    在watch里面写第三个属性即可
  2. 那么deep应该如何写?会出现什么问题?
    在reactive生成的响应式数据自动开启了deep监视,导致deep配置项无效 且无法关闭
  3. 那么reative的数据是如何监视以及出现的问题?
    如果直接写watch一个reactive的对象 那么会出现new和old值一样的问题

    暂时没有办法解决这个问题
    4 因为深度监视没办法关闭 开着会浪费性能的问题,所以如果想监视一个reative响应式对象中的一个数据 直接使用对象.属性是不行的,vue3规定必须要返回这个值
    1
    2
    3
    watch(()=>person.age,(newValue,oldValue)=>{
    console.log('xxx变化了',newValue,oldValue);
    })
  4. 如果要监视reactive定义的一个对象中的某些属性
    1
    2
    3
    watch([()=>person.age,()=>person.name],(newValue,oldValue)=>{
    console.log('xxx变化了',newValue,oldValue);
    })
    看起来很麻烦= =
  5. 特殊情况
  6. watch中的value问题
    如果是一个ref定义的基本数据,那么取出来我们需要用到xx.value
    而在watch里面想监视它 则不能写点value 因为watch监视的是一个结构 而不是一个具体的数据
    1
    2
    3
    watch(sum,(newValue,oldValue)=>{
    console.log('aaa',newValue,oldValue);
    })
    但是如果是ref定义的obj类型 即对象类型 那么用watch监视有两种方法
    一是监视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
5
watchEffect(()=>{
const x1 = sum;
const x2 = person.job.j1.salary;
console.log('回调执行')
})

刚刚说了 用到谁就去监视谁 所以在这里面 用到了sum 和person.job.j1.salary这两个变量,前者好理解 就是一个基本的数据检测 但后者是深度的数据 结果是也能检测出来.说明这个方法非常的智能,可以深度检测.
总结一图流:

  • computed和watchEffect很像的原因?
    前者是有数据变化了,整个都要重新计算然后返回结果
    后者也是一样 有东西变了 就执行回调
  • 两者不一样的地方?
    前者更注重的是回调的结果 所以要有一个返回值
    而后者更注重过程 所以不用写返回值

vue3生命周期

  1. 在vue3里面 原先vue2中的beforeDestroy和destroyed变成了beforeUnmounted和unmounted
  2. 原先在vue2里面 如果创建了new Vue并且传入了配置项,但不写el 也不在vm中挂载el,意味着此时模板没有被解析 但是它经过了beforeCreate和created两个钩子 这样的情况是不必要的,在vue3中得到了改善,vue3需要app挂载完后才能走下一步

  3. vue3也提供了组合api形式的生命周期钩子 和vue2中的对应如下

hook

其实本质上就是js的模块化

定义一个hook文件夹 将需要的函数放进去并暴露方法最后return

和mixins有点类似

toRef

解决响应式丢失的问题

如果我们return变量的时候想调用的时候简写,比如:

1
2
3
4
return {
name:person.name,
age:person.age
}

这个时候数据虽然能用 但因为只是把响应式的数据赋值给了变量,而变量不是响应式的 所以就丢失了响应式.
使用toRef或者toRefs方法 即可把数据变成响应式的,从而return的变量也是响应式的
并且非常需要注意的是 toRef之后修改的数据 对应原先的数据也会修改!!!.
1
2
const name = toRef(person,'name');
console.log(name)//输出的是一个RefImpl对象

注意 对于深层次的数据变成响应式需要这样写
1
const salary = toRef(person.job.j1,'salary');

如果需要抛出的变量很多 那么建议使用toRefs
1
2
3
4
5
6
7
const 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可以使得数据变成只读,虽然它是响应式的但是不允许更改
引申出页面没有变化的两种情况

  1. 数据不是响应式的 vue就检测不到
  2. 数据不可以被修改 readonly属于这种情况
    shallowReadonly让数据的第一层不能被修改 但深层次可以被修改

toRaw还原对象

toRaw用于还原对象或者变量的响应式。
只能处理reactive定义的响应式对象
markRaw
用之前讲讲不用它的时候的需求
如果已经将对象变成响应式并且暴露出去了,那么后面有个function想在刚刚的对象中添加新数据进去是不行的,因为toRefs只把第一层的数据暴露了出去,新添加的数据是没有暴露的。解决方法有两种

  1. 给原先的对象赋一个空的新数据,这样就会把空数据暴露出去且是响应式,所以后面增加也会变动
  2. 将对象重新交出去(交了两次 一次是源对象 一次是toRefs)这样一来就是把整个响应式对象交了出去,既然是响应式的对象,那么也能检测到添加和删除的元素。

那么markRaw的业务是什么呢?
上面说了新添加的响应式数据 如果这个数据特别庞大,层次特别丰富,只是展示到页面上,且不用变动数据的时候,那么这时候这个数据最好就不用响应式。这样一来就需要用到markRaw标记这个数据,这样一来不管这个数据是进入到了响应式对象里,还是一开始给了它响应式,最后它都不会是响应式的。

customRef

创建一个自定义的ref 一般用于防抖的业务场景

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
setup(){
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 就不会造成这样的问题