ps:这一篇从起稿大纲至今天结稿,历经四天之久。
写的心焦手酸,自己也没预判到会写这么多
文中可能出现诸多错别字还请自行忽略、多多包涵,后续会骤步完善
我刚入手vue3时,为了能快速上手介入项目工程,当时也只粗浅的看了个皮毛。
时至今日仍然有很多需要去不断加深学习的地方。
vue3 从2020年9月18日正式发布,距今问世3年多。
这期间也经历了近5000+次提交,
截止目前我正在用的版本已经是v3.4.7。
如果看到这篇文章的你,学习vue3的初忠跟我一样,只是想快速开展工作,
那这一篇应该足够你应付工作日常的需求
但如果你想更深层的研究vue3,建议还是仔细拜读官主文档来更系统性的学习
读到此篇文章的人,我首先默认您已经熟练掌握了 vue2
和 typescript
。
所以这里会着重就vue2与vue3两者存在区别的部分,以下所有案例将不再对ts上多做笔墨,
重心会尽可能的落在工作中常用的场景。不足之处,望请多多指正!
vue2(以下简写为v2) 和 vue3(以下简写为v3)
创建vue3的工程
v3仍继续支持用 vue-cli
来创建工程,但从vue3开始官方更推荐用 Vite
来创建,个人感觉用vite创建工程会更加快捷
Vite 简述
在 Vite
未诞生之前 Webpack
一直是前端构建工具中的老大哥,而 vite
作为新一代的构建工具也具有着他独特的优势。
上图我们可以发现webpack
每次运行都会在route和module上(图中蓝框部分)消耗过多的性能
这就意味着我们项目中的路由和模快越多,启动和更新的速度就会越慢
而 vite
的按需编译
只会启动和预加载更新你所访问的路由和模块(说白了就是你访问哪个模快的路由和model我就只预加载编译哪部分内容) 这样在处理速度上做到尽乎极速
简单总结:
热重载(HMR)
更轻量快速,可以极速启动服务- 相对
Typescript、jsx、css
的开箱即用,不用额外配置任何 plug 就能直接上手(默认支持TS) - 真正的按需编译,不用再等整个应用编译完
更详细的介绍可翻阅(vite官方文档)
1. 性能上的卓越提升
相比v2,v3性能上很卓越的提升,V3体积更小、初次渲染和更新渲染时更快 更节省内存,相项性能的指标至少提升了50%以上
3. 新特性
这里我会例的稍微详细些
3.1 组合式API:
v2 是OptionsAPI(也就是配置式的API) 而 v3 是CompositionAPI(组合式的API)
配置式API增改需 求时常常需要分别修改: data、methods、computed 非常不便于维护
3.2 setup
举例:
1 | // <!-- vue2 --> |
3.3 响应式 ref和reactive
v3中的ref和v2的不同,v3的ref是 用来定义基本类型
和对象类型
的响应式数据的函数 。reactive
属于ref处理对类例的底层子方法,只能定义对象类型
<array | object>的响应式数据。
3.3.1 区别
ref 创建的变量 必段用
.value
(可以用volar插件自动添加来提高编码效率)reactive 重新分配一个对象,会
失去
响应式(可使用Object.assign
去整体替换)
例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<script setup lang="ts" >
{/* reactive */}
const Person = reactive(
{
name'LinJie',
sex:'Female',
hobby:'handsome guy'
}
)
// 方法
function changeObject(){
Object.assign(Person,{name:'林杰',sex:'女',hobby:'男'})
//to do something....
}
<!-- 用 ref 可直接赋值 -->
const Person = ref({name'LinJie',sex:'Female',hobby:'handsome guy'})
// 方法
function changeObject(){
Person.value = {name:'林杰',sex:'女',hobby:'男'}
//to do something....
}
</script>
3.3.2 使用原则
基本类型
只能用ref响应式对象,层级不深
ref,reactive两者皆可
响应式 且层级较深
推荐用reactive
3.3.3 toRefs与toRef
- 作用: 将响应式对象中每一个属性转换为 ref 对象(常用于对象解构后的情况)
- 区别:
toRefs
与toRef
功能大至一致,但toRefs 可以支持批量
转换
1 | let {name,sex} = toRefs(Person); //批量转换 |
3.4 computed 和 watch
3.4.1 computed
的return结果也是个Ref响应式数据,跟v2相同,computed中所关联的数据只要发生变化就会触发computed 的句柄
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
// template 部分
<div>
姓: <input type="text" v-model="firstName"/><br/>
名: <input type="text" v-model="lastName"/><br/>
全名:<span>{{ fullName }}</span>
</div>
<button @click="changeFullName()"></button> // js部分
let firstName = ref('zheng');
let lastName = ref('linJie');
// 1)常规用法=>只读
let fullName = computed(()=>{
return '你想返回的数据'
})
// 2)可读可写用法
let fullName = computed({
get(){
return `${firstName.value.slice(0,1).toUpperCase}${first.value.slice(1)}${lastName.value}`
},
set(computedVal){
// 将computed处理后的值分别拆分赋值
const [firstName,lastName] = val.split('-')
firstName.value = firstName;
lastName.value = firstName;
}
})
changeFullName(){
// fullName作为计算属性的性常规用法中是不允许更改的
// 若需要更改就要用到getter和setter 同v2
fullName.value = "Z-LJ"
}
3.4.2 watch
watch
可以检测四种数据
ref
定义的数据reactive
定义的数据- 函数返回一个值 (
getter
函数) - 包含以上三种内容的数组
我们定义个需求,一个求和方法,当满足大于3时,触发 watch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import {ref,watch} from 'vue'
// data
let sum = ref(0);
// func
function changeSum(){
sum.value +=1
}
// watch if sum>3 时停止解除监视
// watch的返回值是一个remove watch函数
let stopWatch = watch(sum,(nVal,oVal)=>{
if(nVal>3){
stopWatch()
}
})
情况二:watch【ref或reactive】对象数据
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
// template部分
<div>{{person.name}}</div>
<div>{{person.age}}</div>
<button @click="changePerson">修改整 个对象</button>
// script 部分
let person = ref({
name:'林杰',
age:18,
car:{
c1:'宝马',
c2:'奔驰'
}
})
function changePerson(){
person.value={name:'linJie',age:19}
/**
* 当person是reactive定义的
* 用 Object.assign(person,{name:'xx',age:'xxx'})
*/
}
/**watch 对象地址值,
* 1.由于 person 的命名空间变化了,对象整体被替换,
* nVal和oVal产生变化
* 2. 本例中 只更新单个属性值时,由于只变化了对象属性内容,而对象的命名空间没有变化所以 nVal和oVal不会产生变化
*
*/
watch(person,(nVal,oVal)=>{
},{
/*
* 1. 若想watch对象中某一属生的变化,需手动开始深 度监视,
* 2. 如person是reactive的数据,默认开启deep,可省略此步 且不可关闭
*/
deep:true,
immediate:true//同v2
})
/* 若想让对象属性改变后的nVal和oVal产生变化,需要将watch的属性字段改为一个getter
* 下面例子我们用getter来监听person.name,这时属性改变后的nVal和oVal可产生变化
*/
//...()=>{return person.name},经简化为如下
/** 如这里watch的是car ,这里可以直接写成person.
* cart 但这里关注的只是地址值,,
* 若需达到最佳实践,尽量写成计算函数,并增加deep:true 来关注对象内部的细支未节
* */
watch(()=>person.name,(nVal,oVal)=>{
// to do something...
},{
deep:true,
})
/**
* 同时监听多个数据时
*/
watch([
()=>person.name,
()=>person.car.c1
],(nVal,oVal)=>{
console.log(nVal,oVal)// => [nName,nCar],[oName,oCar]
},{
deep:true
})
注意:当watch ref和reactive定义的【对象类型】数据中的某个属性时
- 若该属性值 不是【对象类型 】,需要写成函数形式
- 若该属性值仍然是【对象类型】,可直接编,也可写成函数,但
建议仍然写成函数
3.4.2 watch
和 watchEffect
对比watchEffect
有点像watch
的语法糖,解决了 watch解决了监听多个数据时,写很过冗余的代码的痛点,同时集合了 watch+immediate,并能响应式追踪其依赖的变化。
- 都能监听响应式数据的变化,不同的是监听的方式不同
watch
:要明确指出监听的数据watchEffect
:不用蝗确监听的数据(函数中用到哪些属性,就监听哪些)
用法:
1
2
3
4
5
6watchEffect(()=>{
if(guy.age<='30' && guy.handsome = true){
console.log('Give me 抓回来当压寨夫人!')
}
})
3.5 标签属性ref
和 props
- 这里的
ref
跟上述中的ref
就是雷锋
和雷锋塔
的区了。而是跟v2 中的ref 相同,用来存储dom
标签中的内容,方便引用数据。
用法上略有不同的是。v3 的 Ref 如果写在了Component
上时,v2,可以任意引用子组件实例中的方法和数据,而v3 则需要用defineExpose
按需引出 - v3 的
props
和好 用法上略有不同的是,当子组件接收你组件传来的数据时,需要用defineProps
来接收
注意 新版本的v3 类似于defineExpose
和 defineProps
这种前缀带-define
的宏函数
,可以无需 import
可以在实例中直接引用
举例
1 | // 父组件 |
3.6 vue3 的生命周期函数和钩子
v2 生命周期是 创建、挂载、更新、销毁四个阶段
vue3 不同的是 创建阶段在setup
语法糖中自动开启,销毁 概念上称为 卸载
其它阶段如下:
1 | import {onBeforeMount,onMounted,onBeforeUpdate} from 'vue' |
以此类推更新和卸载 就是 onBeforeUpdate
、onUpdate
、onBeforeUmmount
、onUmmount
…
常用的钩子: onMounted、onUpdate、onBeforeUmmount
.
自定义 Hooks
v3 中的 hooks
把模快发挥到极致,形态上有点类似于 v2中的mixin
举个栗子 做一个求和hooks
1 | // @/hooks/useSum.ts |
3.7 路由
在SPA(single page web application)应用中
路由组件通常放在page
或views
文件夹,一般组件通常放在components
中,在导航中当前显示的组件被挂载,其它未显示的是被卸载
的组件
3.7.1 路由的工作模式 query 和 params及 路由的props
- history模式:
优点: 没有#
,
缺点: 上线后需要服务端处理路径问题,否则刷 新会有404
错误 - hash模式:
优点: 兼客性更好上线后不需要服务端处理路径问题,
缺点:有#
,,不利于SEO1
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
543. 传 `params` 参数时,使用 `to` 对象写法,须使用 `name` 配置项,不能用`path`,传递 `params`参数时,需要提前在规则中占位
4. `props` 配置 可以让路由参数作为 `props` 传递给组件
import {
createRouter,
createWebHistory,//history模式
createWebHashHistory//hash模式
} from 'vue-router'
import XXX from '@/views/XXX.vue'
const router = createRouter({
history:createWebHistory(),
routes:[
{
name:'xxx',
path:'/xxx',
component:XXX
children:[
{
name:'params',
// params的用法 `?`代表非必需
path:'/param/:id/:title?'
component:XXX,
// props的第一种用法,可以让路由收到的所有params参数作为 `props` 传递给组件
// props:true,
// props的第二种用法,可以决定自己决将什么做为props传递给组件
props(route){
return route?.query
}
}
]
}
]
})
// 业务模块
<ul>
<li>{{query.id}}</li>
{/* 从props中拿到的值 */}
<li>{{title}}</li>
</ul>
<script lang="ts">
import {toRefs} from 'vue'
import {useRoute} from 'vue-router'
{/* 第一种 用法 query */}
let route = useRoute();
let {query} = toRefs(route);
{/* 第二种 用法 props */}
defineProps(['title'])
</script>
3.7.2 路由的 replace
和 push
两者区别是 push
会帮助我们的路由自动产生历史记录,可以很好的方便我们使用浏览器的 前进和后退,而 replace
不会产生历史记录.
常见的编程式路由导航:
1 | import {useRouter} from 'vue-router' |
3.8 Pinia 集中式状态管理
状态管理大多应用场景是用在多组件共享数据的时候
3.8.1 Pinia的安装
1 | npm i Pinia |
1 | // 引入Pinia |
3.8.2 存储和读取数据
1 | // @/store/count.ts |
区别: pinia 中可以直接改store中的数据。而vuex是不被允许的。
3.8.3 pinia中的订阅 $subscribe的使用
$subscribe 作用类似于 watch
区别是 $subscribe 用在 状态管理中
用法(这里我们优化下上面的代码 ,把store变成组合式的写法)
1 | // @/store/count.ts |
3.9 vue3的组件的通信方式
3.9.1 props
props 是使用频率最高的一种通信方式.
- 若
父传子
属性值是非函数 - 若
子传父
属性值是函数
1 | // 父组件 |
3.9.2 自定义事件defineEmits
emit 的官方的建设是用 kebab-case
(烤串式)
1 | // 父组件 |
3.9.3 通信方式之 mitt
工具(发布订阅模式)
安装 mitt
工具(类似v2的eventBus)
1 | npm i mitt |
1 |
|
3.9.4 通信方式之 v-model
3.9.5 通信方式之$attrs
(适用于祖孙之间组件通信:双向)
1 | // 父 |
3.9.6 通信方式之$refs
与 $parent
1 | // 父 |
3.9.6 通信方式之provide
与 inject
(适用于祖孙之间组件通信)
1 | // 祖 |
4.0 插槽
v3 的默认插槽
、具名插槽
、以及作用域插槽
与v2 用法完全一致数据在子组件中,但生成的结构由父组件决定
,
就好比(压岁钱放在孩子 子那,但要用来买什么东西,却由父亲决定一样)
这里不再赘述
5.0 其它需要了解的API
V3提供的API很多,常用到的不多,满足日常工作以上内容大部分可以cover到,以下知识点可以做为简单概念了解。现用现查即可
5.1 shallowRef
与 shallowReactive
shallowRef
与 shallowReactive
创建的是一个浅层的响应式对象,最多会让对象的最顶层属性变成响应式,内部嵌套较深的部分不会变
主要目地是为了让属性访问变的更快,更好的提升性能
6.0 readonly 与 shallowReadonly
作用: 用于创建一个对象的深只读副本
对象嵌套属性都为只读,任何尝试修改都会被助止
shallowReadonly
只作用于对象的顶层属性,用到的场景较少
用法:
1 | const original = reactive({...}) |
7.0 toRaw 与 markRaw
toRaw 用于获取一个响应式对象的原始对象 返回不再是响应式,且不触发视图更新
markRaw 标记一个对象 使其永远不会变成响应式数据
8.0 customRef的使用
作用:自定义一个Ref,并对其依赖项跟踪和更新触 发进行逻辑控制
写法有点像 computed
日常需求常用来封装成hooks
1 | // hooks 组件 |
Teleport
一种将我们的组件html结构
移动到指定位置的技术 (有点像传送门)和插槽的作用
1 | // app.vue |
Suspense(用的不多)
等待异步组件时常理染一些客外的内容,让应用有更好的用户体验
使用步骤,
- 导步引入组件
- 使用
Suspense
包裹,并配置好default
与fallback
全局API转移到应用对象
app.component ==> 注册全局组件
app.directive ==> 全局指令
app.mount 挂载根App
app.unmount 缺载根App
app.use
Vue3非兼容性改变
这里的内容我觉得非常重要。
也是我面试侯先人的过程中问的最喜欢问的问题
点击这里翻阅官方文档vue3非兼容性改变
其它细节:
- vue3 的model中不再需要根标签
data
选项应始终被声明为一个函数、移除keyCode
支持作为v-on
的修饰符、v-model
指令被重新设计了v-for
和v-if
在同一个元素身上使用的时优先级发生了变化、- 移除了v2一些常用的方法:如
过滤器filter
、··、、$children
、propert
$on
$off
$once
等等 - v3 弱化了this
- 本文作者: 林杰
- 本文链接: http://linjiefe.github.io/2024/01/11/关于vue3/