前言

vue3的学习记录


vue3新的特性

Composition API(组合API)

  • setup配置
  • ref与reactive
  • watch与watchEffect
  • provide与inject
  • ……
  1. 新的内置组件
  • Fragment
  • Teleport
  • Suspense
  1. 其他改变
  • 新的生命周期钩子
  • data 选项应始终被声明为一个函数
  • 移除keyCode支持作为 v-on 的修饰符
  • ……

vite

1
2
3
4
5
6
7
## 不需要全局安装
## 创建工程
yarn create vite
## 安装依赖
yarn
## 运行
yarn dev

参考:vue3保姆级教程


vue3-cli项目分析

1
2
3
4
5
6
7
8
9
10
// main.js 分析
// 没有了 Vue 的构造函数,只是createApp的工厂函数,可以直接调用
// 创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”)
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

// 其他文件
// Vue3组件中的模板结构可以没有根标签

setup函数

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
/*
1. 理解:Vue3.0中一个新的配置项,值为一个函数。
2. setup是所有Composition API 表演的舞台。
4. 组件中所用到的:数据、方法等等,均要配置在setup中。
5. setup函数的两种返回值:
1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
2. 若返回一个渲染函数:则可以自定义渲染内容。
6. 注意:
1. 尽量不要与Vue2.x配置混用
Vue2.x配置(data、methos、computed...)中 可以访问到 setup中的属性、方法。
但在setup中 不能访问到 Vue2.x配置(data、methos、computed...)。
如果有重名, setup优先。
2. setup不能是一个async函数,因为返回值不再是return的对象, 而是promise,
模板看不到return对象中的属性。
(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
*/

// 示例
<template>
<!-- name: '张三'sex: '男'a: 200 -->
<h2>姓名:{{name}}</h2>
<h2>性别:{{sex}}</h2>
<h2>a的值是:{{a}}</h2>
<button @click="sayHello">说话(Vue3所配置的——sayHello)</button>
<button @click="sayWelcome">说话(Vue2所配置的——sayWelcome)</button>
<button @click="test1">测试一下在Vue2的配置中去读取Vue3中的数据、方法</button>
<button @click="test2">测试一下在Vue3的setup配置中去读取Vue2中的数据、方法</button>
</template>

<script>
// import {h} from 'vue'
export default {
name: 'App',
data() {
return {
sex:'男',
a:100
}
},
methods: {
sayWelcome(){
alert('欢迎学习vue3')
},
test1(){
console.log(this.sex)
console.log(this.name) // 可以访问到 setup 返回的数据
console.log(this.sayHello) // 可以访问到 setup 返回的数据
}
},
// 此处只是测试一下setup,暂时不考虑响应式的问题。
// setup 不能使用 async
setup(){
// 数据
let name = '张三'
let a = 200

//方法
function sayHello(){
alert(`我叫${name}`)
}
function test2(){
console.log(name) // 张三
console.log(sayHello)
console.log(this.sex) // 打印不到数据
console.log(this.sayWelcome) // 打印不到数据
}

//返回一个对象(常用)
return {
name,
sayHello,
test2,
a
}

// 返回一个函数(渲染函数),需要引入 h函数,使用后template的模板将失效
// return ()=> h('h1','尚硅谷')
}
}
</script>

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
37
38
39
40
41
42
43
44
/*
- 作用: 定义一个响应式的数据
- 语法: const xxx = ref(initValue)
- 创建一个包含响应式数据的 引用对象(reference对象,简称ref对象)。
- JS中操作数据: xxx.value
- 模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>
- 备注:
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠 Object.defineProperty() 的 get 与 set 完成的。
- 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数 reactive 函数。
*/

// 示例
<template>
<h2>姓名:{{name}}</h2>
<h3>工作种类:{{job.type}}</h3>
<button @click="changeInfo">修改人的信息</button>
</template>

<script>
import {ref} from 'vue'
export default {
name: 'App',
setup(){
// 数据
let name = ref('张三')
let job = ref({
type:'前端工程师'
})

// 方法
function changeInfo(){
name.value = '李四'
job.value.type = 'UI设计师' // 不需要job.value.type.value
}
// 返回一个对象
return {
name,
job,
changeInfo
}
}
}
</script>

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
/*
- 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用 ref 函数)
- 语法:const 代理对象= reactive(源对象) 接收一个对象(或数组),
返回一个代理对象 Proxy 的实例对象,简称proxy对象
- reactive定义的响应式数据是“深层次的”。
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
*/

// 示例
<template>
<h1>一个人的信息</h1>
<h2>姓名:{{person.name}}</h2>
<h3>工作种类:{{person.job.type}}</h3>
<button @click="changeInfo">修改人的信息</button>
</template>

<script>
import {reactive} from 'vue'
export default {
name: 'App',
setup(){
//数据
let person = reactive({
name:'张三',
job:{
type:'前端工程师',
hobby:['抽烟','喝酒','烫头']
})
// 方法
function changeInfo(){
person.name = '李四'
person.job.type = 'UI设计师'
person.hobby[0] = '学习'
}

//返回一个对象(常用)
return {
person,
changeInfo
}
}
}
</script>

Vue3.0中的响应式原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
- 实现原理:
- 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
- 通过Reflect(反射): 对源对象的属性进行操作。
*/
new Proxy(data, {
// 拦截读取属性值
get (target, prop) {
return Reflect.get(target, prop)
},
// 拦截设置属性值或添加新属性
set (target, prop, value) {
return Reflect.set(target, prop, value)
},
// 拦截删除属性
deleteProperty (target, prop) {
return Reflect.deleteProperty(target, prop)
}
})
proxy.name = 'tom'

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
// vue2 的数据 需要 $set 和 $delete

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<script type="text/javascript" >
//源数据
let person = {
name:'张三',
age:18
}

//模拟Vue2中实现响应式,使用//#region //#endregion和可以关闭烦人的自动展开
//#region
/* let p = {}
Object.defineProperty(p,'name',{
configurable:true,
get(){ //有人读取name时调用
return person.name
},
set(value){ //有人修改name时调用
console.log('有人修改了name属性,我发现了,我要去更新界面!')
person.name = value
}
})
Object.defineProperty(p,'age',{
get(){ //有人读取age时调用
return person.age
},
set(value){ //有人修改age时调用
console.log('有人修改了age属性,我发现了,我要去更新界面!')
person.age = value
}
}) */
//#endregion

//模拟Vue3中实现响应式
//#region
const p = new Proxy(person,{
//有人读取p的某个属性时调用
get(target,propName){
console.log(`有人读取了p身上的${propName}属性`)
return Reflect.get(target,propName)
},
//有人修改p的某个属性、或给p追加某个属性时调用
set(target,propName,value){
console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`)
Reflect.set(target,propName,value)
},
//有人删除p的某个属性时调用
deleteProperty(target,propName){
console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
return Reflect.deleteProperty(target,propName)
}
})
//#endregion

let obj = {a:1,b:2}
//通过Object.defineProperty去操作
//#region
/* try {
Object.defineProperty(obj,'c',{
get(){
return 3
}
})
Object.defineProperty(obj,'c',{
get(){
return 4
}
})
} catch (error) {
console.log(error)
} */
//#endregion

//通过Reflect.defineProperty去操作
//#region
/* const x1 = Reflect.defineProperty(obj,'c',{
get(){
return 3
}
})
console.log(x1)

const x2 = Reflect.defineProperty(obj,'c',{
get(){
return 4
}
})
if(x2){
console.log('某某某操作成功了!')
}else{
console.log('某某某操作失败了!')
} */
//#endregion

// console.log('@@@')
</script>
</body>
</html>

setup 的参数

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
/*
setup执行的时机:
在beforeCreate之前执行一次,this是undefined。
setup的参数:
1.props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
2.context:上下文对象
attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs。
slots: 收到的插槽内容, 相当于 this.$slots。
emit: 分发自定义事件的函数, 相当于 this.$emit。
注意:props和emits不接受会有警告
*/

// 示例
// 父组件
<template>
<Demo @hello="showHelloMsg" msg="你好啊">
<template #slotName>
<span>具名插槽</span>
</template>
</Demo>
</template>

<script>
import Demo from './components/Demo'
export default {
name: 'App',
components:{Demo},
setup(){
function showHelloMsg(value){
alert(`你好啊,你触发了hello事件,我收到的参数是:${value}!`)
}
return {
showHelloMsg
}
}
}
</script>

// 子组件
<template>
<h1>一个人的信息</h1>
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<button @click="test">测试触发一下Demo组件的Hello事件</button>
</template>

<script>
import {reactive} from 'vue'
export default {
name: 'Demo',
props:['msg'],
emits:['hello'],
setup(props,context){
// console.log('---setup---',props)
// console.log('---setup---',context)
// console.log('---setup---',context.attrs) //相当与Vue2中的$attrs
// console.log('---setup---',context.emit) //触发自定义事件的。
console.log('---setup---',context.slots) //插槽,使用新的api v-slot,slot=""打印没有名字
//数据
let person = reactive({
name:'张三',
age:18
})

//方法
function test(){
context.emit('hello',666)
}

//返回一个对象(常用)
return {
person,
test
}
}
}
</script>

computed 计算属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import {computed} from 'vue'

setup(){
...
// 计算属性——简写
let fullName = computed(()=>{
return person.firstName + '-' + person.lastName
})
// 计算属性——完整
let fullName = computed({
get(){
return person.firstName + '-' + person.lastName
},
set(value){
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
}

watch函数

与Vue2.x中watch配置功能一致,两个小“坑”:

  1. 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。

  2. 监视reactive定义的响应式数据中对象时:deep配置有效。

  3. watch 监听对象的某个具体的值时,可以获取到oldVlaue。

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
//数据
let sum = ref(0)
let msg = ref('你好啊')
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})

//情况一:监视ref定义的响应式数据
watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
},{immediate:true})

//情况二:监视多个ref定义的响应式数据
watch([sum,msg],(newValue,oldValue)=>{
// newValue与oldValue获取的也是数组形式
console.log('sum或msg变化了',newValue,oldValue)
})

/* 情况三:监视reactive定义的响应式数据
若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
*/
watch(person,(newValue,oldValue)=>{
// 任意的person里面的数据变化了,就会执行
console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不再奏效

//情况四:监视reactive所定义的一个响应式数据中的某个属性
watch(()=>person.name,(newValue,oldValue)=>{
console.log('person的name变化了',newValue,oldValue)
},{immediate:true,deep:true})

//情况五:监视reactive定义的响应式数据中的某些属性
watch([()=>person.name,()=>person.name],(newValue,oldValue)=>{
console.log('person的name或age变化了',newValue,oldValue)
},{immediate:true})

//特殊情况
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive定义的对象中的某个属性,所以deep配置有效


// 注意:如果使用ref定义对象,watch需要写xxx.value,
// 因为watch监听的是一个Proxy对象或者是ref对象,如不能使用ref定义字符串,不可具体值xxx.value

watchEffect函数

  1. watch的套路是:既要指明监视的属性,也要指明监视的回调。
  2. watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
  3. watchEffect有点像computed:
    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
1
2
3
4
5
6
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
watchEffect(()=>{
const x1 = sum.value
const x2 = person.age
console.log('watchEffect配置的回调执行了')
})

vue3 的生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 注意:Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
/* beforeDestroy改名为 beforeUnmount
destroyed改名为 unmounted */
// -----------
// Composition API 形式的生命周期钩子
/*
beforeCreate===>setup()
created=======>setup()
beforeMount ===>onBeforeMount
mounted=======>onMounted
beforeUpdate===>onBeforeUpdate
updated =======>onUpdated
beforeUnmount ==>onBeforeUnmount
unmounted =====>onUnmounted
*/

// 注意:同时配置项和composition Api 都写得话,先执行setup里面的

自定义Hook

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
// hook,本质是一个函数,把setup函数中使用的Composition API进行了封装。类似于vue2.x中的mixin。
// 使用示例
// 在/src/hooks/usePoint.js 定义 获取鼠标点的hook
import {reactive,onMounted,onBeforeUnmount} from 'vue'
export default function (){
//实现鼠标“打点”相关的数据
let point = reactive({
x:0,
y:0
})
//实现鼠标“打点”相关的方法
function savePoint(event){
point.x = event.pageX
point.y = event.pageY
console.log(event.pageX,event.pageY)
}
//实现鼠标“打点”相关的生命周期钩子
onMounted(()=>{
window.addEventListener('click',savePoint)
})
onBeforeUnmount(()=>{
window.removeEventListener('click',savePoint)
})
return point
}
// 组件中使用
import usePoint from '../hooks/usePoint'
export default {
name: 'Demo',
setup(){
let point = usePoint()
return point
}
}

toRef

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
/* 
作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
语法:const name = toRef(person,'name')
应用: 要将响应式对象中的某个属性单独提供给外部使用时。
扩展:toRefs 与 toRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
*/

<template>
<h4>{{person}}</h4>
<h2>姓名:{{name}}</h2>
<h2>薪资:{{job.j1.salary}}K</h2>
<button @click="name+='~'">修改姓名</button>
<button @click="job.j1.salary++">涨薪</button>
</template>

<script>
import {ref,reactive,toRef,toRefs} from 'vue'
export default {
name: 'Demo',
setup(){
//数据
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})
return {
person,
// name:toRef(person,'name'),
// salary:toRef(person.job.j1,'salary'),
...toRefs(person)
}
}
}
</script>

// 注意:直接return {name: person.name} 不是响应式,如下
let obj = { a: 1}
let b = obj.a
console.log(obj.a) // 1
obj.a = 2
console.log(obj.a) // 2
console.log(b) // b还是1,并没有修改
// 直接 name:Ref(person.name),会导致修改了新的ref对象name,不修改原person数据

shallowReactive 与 shallowRef

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
/*
shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
什么时候使用?
如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
*/

<template>
<h4>当前的x.y值是:{{x.y}}</h4>
<button @click="x={y:888}">点我替换x</button>
<button @click="x.y++">点我x.y++</button>
<hr>
<h4>{{person}}</h4>
<h2>姓名:{{name}}</h2>
<h2>薪资:{{job.j1.salary}}K</h2>
<button @click="name+='~'">修改姓名</button>
<button @click="job.j1.salary++">涨薪</button>
</template>

<script>
import {ref,reactive,toRef,toRefs,shallowReactive,shallowRef} from 'vue'
export default {
name: 'Demo',
setup(){
//数据
let person = shallowReactive({ //只考虑第一层数据的响应式
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})

let x = shallowRef({
y:0
})

return {
x,
person,
...toRefs(person)
}
}
}
</script>

readonly 与 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
28
29
30
31
32
33
34
35
/*
readonly: 让一个响应式数据变为只读的(深只读)。
shallowReadonly:让一个响应式数据变为只读的(浅只读)。
应用场景: 不希望数据被修改时。
*/

<script>
import {ref,reactive,toRefs,readonly,shallowReadonly} from 'vue'
export default {
name: 'Demo',
setup(){
//数据
let sum = ref(0)
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})

person = readonly(person)
// person = shallowReadonly(person)
// sum = readonly(sum)
// sum = shallowReadonly(sum)

return {
sum,
...toRefs(person)
}
}
}
</script>

toRaw 与 markRaw

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
/*
toRaw:
作用:将一个由reactive生成的响应式对象转为普通对象。
使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
markRaw:
作用:标记一个对象,使其永远不会再成为响应式对象。
应用场景:有些值不应被设置为响应式的,例如复杂的第三方类库等。
当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
*/

<script>
import {ref,reactive,toRefs,toRaw,markRaw} from 'vue'
export default {
name: 'Demo',
setup(){
let sum = ref(0)
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})

//
function showRawPerson(){
const p = toRaw(person) // 变回不是响应式
p.age++
console.log(p)
}

// 添加一个不是响应式的属性
function addCar(){
let car = {name:'奔驰',price:40}
person.car = markRaw(car)
}

return {
sum,
person,
...toRefs(person),
addCar,
showRawPerson
}
}
}
</script>

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
32
33
34
35
36
37
38
39
40
41
// 实现防抖效果:
<template>
<input type="text" v-model="keyword">
<h3>{{keyword}}</h3>
</template>

<script>
import {ref, customRef} from 'vue'

export default {
name: 'Demo',
setup() {
// let keyword = ref('hello') //使用Vue准备好的内置ref
//自定义一个myRef
function myRef(value, delay) {
let timer
//通过customRef去实现自定义
return customRef((track, trigger) => {
return {
get() {
track() //告诉Vue这个value值是需要被“追踪”的
return value
},
set(newValue) {
clearTimeout(timer)
timer = setTimeout(() => {
value = newValue
trigger() //告诉Vue去更新界面
}, delay)
}
}
})
}

let keyword = myRef('hello', 500) //使用程序员自定义的ref
return {
keyword
}
}
}
</script>

provide 与 inject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 作用:实现祖与后代组件间通信
套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据 */

// 祖组件中:
setup(){
......
let car = reactive({name:'奔驰',price:'40万'})
provide('car',car)
......
}

// 后代组件中:
setup(props,context){
......
const car = inject('car')
return {car}
......
}

响应式数据的判断

1
2
3
4
isRef: 检查一个值是否为一个 ref 对象
isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

新的组件

1.Fragment

  • 在Vue2中: 组件必须有一个根标签
  • 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
  • 好处: 减少标签层级, 减小内存占用

2.Teleport

1
2
3
4
5
6
7
8
9
10
// Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。
// to可以是 html、body或者是css选择器
<teleport to="移动位置">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>

3.Suspense (目前好像在试用阶段)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 异步引入组件
import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))

// 使用 Suspense包裹组件,并配置好 default 与 fallback
<div class="app">
<h3>我是App组件</h3>
<Suspense>
<!-- 需要展示的放在default里面 -->
<template v-slot:default>
<Child/>
</template>
<!-- 展位 -->
<template v-slot:fallback>
<h3>加载中.....</h3>
</template>
</Suspense>
</div>

// 使用Suspense后setup可以返回一个promise(即为 async 函数)

其他改变

1.全局API的转移

2.x 全局 API(Vue) 3.x 实例 API (app)
Vue.config.xxxx app.config.xxxx
Vue.config.productionTip 移除
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
Vue.prototype app.config.globalProperties

2.其他

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
// 1.data选项应始终被声明为一个函数。

// ------------------------

// 过度类名的更改:
// Vue2.x写法
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-leave,
.v-enter-to {
opacity: 1;
}

// Vue3.x写法
.v-enter-from,
.v-leave-to {
opacity: 0;
}

.v-leave-from,
.v-enter-to {
opacity: 1;
}

// ------------------
// 移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes

// 移除v-on.native修饰符
<my-component
v-on:close="handleComponentEvent"
v-on:click="handleNativeClickEvent"
/>
// 子组件中声明自定义事件( 不声明则为原生事件)
<script>
export default {
emits: ['close']
}
</script>

// 移除 过滤器(filter

pinia

参考:掘金pinia

优点

  • Vue2和Vue3都支持,这让我们同时使用Vue2和Vue3的小伙伴都能很快上手。
  • pinia中只有state、getter、action,抛弃了Vuex中的Mutation,Vuex中mutation一直都不太受小伙伴们的待见,pinia直接抛弃它了,这无疑减少了我们工作量。
  • pinia中action支持同步和异步。
  • 良好的Typescript支持,毕竟我们Vue3都推荐使用TS来编写,这个时候使用pinia就非常合适了。
  • 无需再创建各个模块嵌套了,Vuex中如果数据过多,我们通常分模块来进行管理,稍显麻烦,而pinia中每个store都是独立的,互相不影响。
  • 体积非常小,只有1KB左右。
  • pinia支持插件来扩展自身功能。
  • 支持服务端渲染。

使用

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
// 创建项目
npm create vite@latest my-vite-app --template vue-ts
// 安装pinia
yarn add pinia
npm install pinia

// main.ts

import { createApp } from "vue";
import App from "./App.vue";
import { createPinia } from "pinia"; // 引入createPinia
const pinia = createPinia(); // 创建pinia实例

const app = createApp(App);
app.use(pinia); // 使用pinia
app.mount("#app");

// 创建store,/src/store/user.ts

import { defineStore } from 'pinia'
// 1.定义并导出容器
// 参数1:容器的ID,必须唯一,将来Pinia会把所有的容器挂载到根容器,每个容器的名字就是这里的ID
export const useUsersStore = defineStore('users', {
/**
* 类似与组件的data, 用来存储全局状态
* 1.必须是函数:这样是为了在服务端渲染的时候避免交叉请求导致的数据状态污染(客户端其实无所谓)
* 2.必须是箭头函数:为了更好的ts类型推导
* 返回值:一个函数,调用该函数即可得到容器实例
*/
state: () => {
return {
count: 100,
foo: 'bar',
arr: [1, 2, 3]
}
},
/**
* 类似于组件的computed,用来封装计算属性,有【缓存】功能
*/
getters: {
// 每个函数接受一个可选参数:state状态对象
// count10(state) {
// console.log('count10()调用了');// 具有缓存功能
// return state.count + 10
// }

// (不建议)如果不使用state而使用this,此时就不能对返回值类型做自动推导了,必须手动指定
count10(): number {
return this.count + 10
}
},
/**
* 完全类比于Vue2组件中的methods(可以直接用this),用来【封装业务逻辑】,修改state
*/
actions: {
/**
* 注意!!不能使用箭头函数定义actions!!一定要用普通函数!!!
* why?因为箭头函数绑定了外部this
*/
changeState(num: number) {
// 可以直接使用this,像极了Vue2
// this.count++
// this.foo = 'hello'
// this.arr.push(4)

// 对于批量修改,建议使用patch做优化
this.$patch((state) => {
state.count += num
state.foo = 'hello'
state.arr.push(4)
})
}
}

})

// 使用store,/src/App.vue


<script setup lang="ts">
import { useUsersStore } from "../src/store/user";
const store = useUsersStore();
console.log(store);
</script>
// 使用store,引入我们声明的useUsersStore 方法即可
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
<template>
<p>{{ mainStore.count }}</p>
<p>{{ mainStore.foo }}</p>
<p>{{ mainStore.arr }}</p>
<p>{{ mainStore.count10 }}</p>
<p>{{ mainStore.count10 }}</p>
<p>{{ mainStore.count10 }}</p>
<hr />
<p>{{ count }}</p>
<p>{{ foo }}</p>
<p>
<button @click="handleChangeState">修改数据</button>
</p>
</template>

<script lang="ts" setup>
import { storeToRefs } from 'pinia'
import { useMainStore } from '../store'

// 【哪里使用写哪里】,此时要在HelloWorld组件中用,那就写这里。这很Composition API
const mainStore = useMainStore()

// 禁止!这样会丧失响应性,因为pinia在底层将state用reactive做了处理
// const { count, foo } = mainStore
// 解决方案:将结构出的数据做ref响应式代理
const { count, foo } = storeToRefs(mainStore)

const handleChangeState = () => {
// ==============数据修改的几种方式=============
// 方式一:直接修改
// mainStore.count++

// 方式二:使用 $patch(对象) 批量修改,建议使用,底层做了性能优化
// mainStore.$patch({
// count: mainStore.count + 1,
// foo: 'hello',
// arr: [...mainStore.arr, 4] // 这就不优雅了,所以有了方式三
// })

// 方式三:使用 $patch(回调函数),这个更优雅,墙裂推荐!!!
// 回调函数中的state参数,就是Store定义时里面的state!
// mainStore.$patch((state) => {
// state.count++
// state.foo = 'hello'
// state.arr.push(4)
// })

// 方式四:逻辑较为复杂时,应封装到Store的actions中,并对外暴露接口
mainStore.changeState(10)
}
</script>

setup 语法糖

setup 取名

参考:参考

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
// 方法一
// 新增一个script标签,在这个script标签定义一个name属性(注意多个script使用时 lang的值要一致)
<script lang="ts">
export default { name: 'Layout'
}
</script>
<script setup lang="ts">
</script>

// 方法二
// 使用unplugin-vue-define-options插件
yarn add unplugin-vue-define-options -D // 安装
// vite.config.js (配置)
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

import DefineOptions from 'unplugin-vue-define-options/vite'

export default defineConfig({
plugins: [vue(), DefineOptions()],
})
// 使用
<script setup lang="ts">
defineOptions({
name: 'Layout'
})
</script>

// 方法三
// 使用 vite-plugin-vue-setup-extend 插件
yarn add vite-plugin-vue-setup-extend -D // 安装
// vite.config.js (配置)
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueSetupExtend from 'vite-plugin-vue-setup-extend'

export default defineConfig({
plugins: [vue(), vueSetupExtend()]
})
// 使用
<script setup lang="ts" name="Layout">

</script>

defineProps()和defineEmits()

使用的编译器宏不需要导入

1
2
3
4
5
6
7
8
9
10
// props 里面的属性,在模板中不需要添加props,在js中需要添加
<script setup>
const props = defineProps({
foo: String
})

const emit = defineEmits(['change', 'delete'])
// setup 代码
</script>
emit('change','xxx') // 使用

defineExpose

1
使用 <script setup> 的组件是默认关闭的,setup 相当于是一个闭包,除了内部的 template模板,谁都不能访问内部的数据和方法。
1
2
3
4
5
6
7
8
9
<script setup>
import { defineExpose } from 'vue'
const a = 1
const b = 2
defineExpose({
a
})
</script>
// 通过 ref获取DOM 既可以拿到数据与方法

Ref 获取 DOM

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
// 获取单独的一个DOM
<template>
<div ref="myRef">获取单个DOM元素</div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
const myRef = ref(null)
onMounted(() => {
console.dir(myRef.value);
})
</script>

// 批量获取DOM(推荐)
<template>
<li v-for="(item, index) in 3" :key="index" :ref="el => { myRef[index] = el }">
{{ item }}
</li>
</template>
// 存储 Dom 数组 myRef
const myRef = ref([])
nextTick(() => {
console.dir(myRef.value)
})

// 第二种方式 (不推荐)
// 注意:ref绑定时不能用push, 会在更新的时候造成bug, 元素会重复。
// 解决:如上的onBeforeUpdate重置下即可
<template>
<div v-for="(item, index) in 3" :ref="setRef">
{{ item }}
</div>
</template>

<script setup>
import { ref, onBeforeUpdate } from 'vue'




// 存储dom数组
const myRef = ref([])
// 确保在每次更新之前重置ref
onBeforeUpdate(() => {
myRef.value = []
})


const setRef = (el) => {
myRef.value.push(el);
}
nextTick(() => {
console.dir(myRef.value);
}

</script>

vue3 父子组件双向绑定

参考:vue2与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
// 父组件
// vue3 中没有.sync修饰符,默认写法
<ChildComponent v-model="msg" />

<!-- 是以下的简写: -->

<ChildComponent :modelValue="msg" @update:modelValue="meg = $event"/>

// 定义绑定的名称
<ChildComponent v-model:message="meg" />

<!-- 是以下的简写: -->

<ChildComponent :message="meg" @update:message="message = $event" />

// 子组件中的使用
const props = defineProps({
message: {
type: String
}
})
const emits = defineEmits(['update:message'])
emits('update:message','新的值')

// 如果绑定到input上,可以使用input事件
// 或者绑定一个克隆新的ref值,监听这个值来出发update

vue3 中css使用js中的变量

1
2
3
4
5
6
7
8
9
<script setup>
import { ref } from 'vue';
const color = ref('red')
</script>
<style>
div{
color: v-bind(color)
}
</style>

获取 slots 和 attrs

1
2
3
4
5
<script setup>
import { useAttrs, useSlots } from 'vue'
const attrs = useAttrs()
const slots = useSlots()
</script>

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
import { useRouter, useRoute } from 'vue-router'
export default {
setup() {
const router = useRouter()
const route = useRoute()
function pushWithQuery(query) {
router.push({
name: 'search',
query: {
...route.query,
},
})
}
},
}

// route 对象是一个响应式对象,它的任何属性都可以被监听,
// 但你应该避免监听整个 route 对象。在大多数情况下,你应该直接监听你期望改变的参数。

import { useRoute } from 'vue-router'
import { ref, watch } from 'vue'

export default {
setup() {
const route = useRoute()
const userData = ref()

// 当参数更改时获取用户信息
watch(
() => route.params.id,
async newId => {
userData.value = await fetchUser(newId)
}
)
},
}

// 请注意,在模板中我们仍然可以访问 $router 和 $route,所以不需要在 setup 中返回 router 或 route。

setup 里面的导航守卫

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
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'

export default {
setup() {
// 与 beforeRouteLeave 相同,无法访问 this
onBeforeRouteLeave((to, from) => {
const answer = window.confirm(
'Do you really want to leave? you have unsaved changes!'
)
// 取消导航并停留在同一页面上
if (!answer) return false
})

const userData = ref()

// 与 beforeRouteUpdate 相同,无法访问 `this`
onBeforeRouteUpdate(async (to, from) => {
//仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id)
}
})
},
}

Vue Router 4 补充

参考:掘金

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1. vue Router 的模式配置
// hash模式使用 createWebHashHistory方法, history模式使用 createWebHistory方法

import { createRouter, createWebHashHistory } from 'vue-router'
import Layout from '@/layout/index.vue'
import Home from '@/views/home/Home.vue'
const routes = [
{
path: '/',
component: Layout,
children: [{ path: '', component: Home }],
},
]

export default createRouter({
history: createWebHashHistory(),
routes,
})
// main.js中使用
import router from './router/index'
createApp(App).use(router).mount('#app')

动态路由匹配

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
// 路由匹配
{ path: '/courseinfo/:title', component: xxx }
// 捕获所有路由或 404 Not found 路由
{ path: '/:pathMatch(.*)*', component: xxx }

/* 匹配语法 "/:paramkey(正则表达式)正则表达式量词"
(正则表达式) 支持完成的正则字面量,表示paramkey的匹配规则
正则表达式量词 用来规定 匹配多少个 / 斜线之间的内容
如:{ path: "/:paramkey(\\w*)?" }
可以匹配 0个 或 一个 / 斜线之后的内容,斜线之间的参数值只能够用字母数字下划线构成
匹配: [/, /a, /bc] 不匹配: [/a/b, /a/b/c]
*/
// 注意:使用\w等预定义符,最好加上\ 确保转义反斜杠

// 路由注意的点
/* 1.获取参数的方式 $route.params.title 只能在模板中使用或
useRoute().params.title必须在setup中调用
因为 setup 中不能使用this,所以 $route 只能在模板中使用。
*/

/* 2.响应参数变化: 使用带有参数的路由时需要注意的是,
当用户从 /courseinfo/javascript 导航到 /courseinfo/python 时,相同的组件实例将被重复使用。
因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。
不过,这也意味着组件的生命周期钩子不会被调用。解决方法:watch监听
$route 对象上的任意属性,在这个场景中,就是$route.params;或者使用导航守卫 beforeRouteUpdate。
*/

/* 删除了*(星标或通配符)路由, 同样path与params不能搭配 */

路由守卫

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
/* 全局守卫 */
// beforeEach afterEach
const router = createRouter({ ... })

router.beforeEach((to, from) => {
// ...
// 返回 false 以取消导航
return false
})

/* 路由独享的守卫 */
{
path: '/loginsuccess',
name: 'LoginSuccess',
component: LoginSuccess,
beforeEnter: (to, from) => {
console.log("----登录成功页面的路由独享的守卫")
}
}

/* 组件内路由守卫 */
// beforeRouteEnter beforeRouteUpdate beforeRouteLeave 不能访问this,通过 vm 访问组件实例
// setup中的守卫 onBeforeRouteUpdate 和 onBeforeRouteLeave两个,没有对应的beforeRouteEnter

// 注意:忽略 mixins 中的导航守卫

动态路由

1
2
3
4
5
6
7
8
9
// 新增路由
router.addRoute({
path: '/xxx',
component: xxx,
name: 'xxx',
})
// 移除路由
router.removeRoute('xxx'); // 通过 name 删除
router.addRoute({ path: '/bbb', name: 'xxx', component: bbb }) // 名字重复时自动覆盖

其他

1
2
3
4
5
6
7
// resolve 返回路由地址的标准化版本。还包括一个包含任何现有 base 的 href 属性。
// 使用 resolve 新窗口打开
const {href} = this.$router.resolve({
path: "/xxx",
query:{ id:xx }
});
window.open(href, '_blank');//打开新的窗口

插槽

参考:CSDN

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
<!-- 只有默认插槽时,组件的标签才可以被当作插槽的模板来使用,这样我们就可以把 v-slot 直接用在组件上,
不然必须放在template标签内 -->
<自定义组件 v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</自定义组件>

缩写语法:
<自定义组件 v-slot="slotProps">
{{ slotProps.user.firstName }}
</自定义组件>


<!-- 动态插槽名 -->
<自定义标签>
<template v-slot:[slotName]>
...
</template>
</自定义标签>


<!-- 注意:如果希望使用缩写,则必须带参数,也就是默认插槽的缩写和这个插槽缩写不能够连用 -->
<自定义标签>
<!-- 会报错 -->
<template #defalut=user>
<h1>{{user.name}}</h1>
</template>
</自定义标签>

全局组件

1
2
3
4
5
6
7
8
import MyComponent from './App.vue'

app.component('MyComponent', MyComponent)
// app.component() 方法可以被链式调用:
app
.component('ComponentA', ComponentA)
.component('ComponentB', ComponentB)
.component('ComponentC', ComponentC)

自定义指令

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
<script setup>
// 在模板中启用 v-focus
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>

// 没有<script setup>
export default {
setup() {
/.../
},
directives: {
// 在模板中启用 v-focus
focus: {
/* ... */
}
}
}

// 自定义指令全局注册
const app = createApp({})

// 使 v-focus 在所有组件中都可用
app.directive('focus', {
/* ... */
})

自定义插件

1
2
3
4
5
6
7
8
9
10
11
12
// 使用插件
import { createApp } from 'vue'
const app = createApp({})
app.use(myPlugin, {
/* 可选的选项 */
})

const myPlugin = {
install(app, options) {
// 配置此应用
}
}

defineComponent

defineComponent 本身的功能很简单,但是最主要的功能是为了 ts 下的类型推到。

类似于vite里面的defineConfing


mitt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
yarn add mitt  // 安装依赖
import mitt from 'mitt'
const emitter = mitt()
emitter.on('foo', e => console.log('foo', e) ) // 绑定事件
emitter.emit('foo', { a: 'b' }) // 触发事件
emitter.all.clear() // 清空所有事件

function onFoo() {}
emitter.on('foo', onFoo)
emitter.off('foo', onFoo) // 删除给定类型的事件处理程序(需要传递两个参数)

// 一般使用方式 mitt.ts,其他 vue 文件引用这个模块
import 'mitt' from 'mitt';
export default mitt()

vue3 补充

项目构建

1
2
3
4
5
6
7
8
9
10
11
// 使用官方的脚手架搭建 npm init vue@latest
// 使用 vite 搭建 npm create vite@latest
// 脚手架的配置会比 vite 全

// 项目中 ts 识别不了 vue 文件、在声明文件 env.d.ts vite-env.d.ts 中添加
// 或者开启插件 TypeScript Vue Plugin (Volar)
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

语法补充

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
/* 1. 事件名称使用变量
<template>
<button @[event]="submit" type="submit">submit</button>
</template>
<script setup lang="ts">
const event = 'click'
const submit = () => {}
</script>
*/

/* 2. v-memo 需要传入一个固定长度的依赖值数组进行比较。
如果数组里的每个值都与最后一次的渲染相同那么整个子树的更新将被跳过。
如:当组件重新渲染,如果 valueA 和 valueB 都保持不变,这个 div 及其子项的所有更新都将被跳过
<div v-memo="[valueA, valueB]">

</div>

与 v-for 使用,确保两者都绑定在同一个元素上。v-memo 不能用在 v-for 内部。
*/

/* key 使用 index 或者不填写时,使用 v-for 逆向添加删除时,input 值会存在问题(无 v-model),
vue3 与 vue2 都会存在。
另外:当 input 有 v-model 绑定了值时,会正常显示,每个 input 都有不同的绑定。
*/

/* ref 类型的两种方式
import type { Ref } from "vue"
const data = ref<{ name: string }>({name: 'xxx'})
const data: Ref<{ name: string }> = ref({ name: 'xxx' })
// 其他
shallowRef 是浅层次的响应式,只支持到 value,
另外 ref 和 shallowRef 不能同时使用,ref 会影响 shallowRef 的变化,造成视图更新。
ref 更新视图会调用 triggerRef 函数,triggerRef() 需要提供 "ref" 的自变量。
shallowReactive 与 reactive 同时使用也会有上面的问题
*/

/* toref 可以解构 ref 里面的对象
const obj = ref({ name: 'John', age: 25 });
const nameRef = toRef(obj.value, 'name');
const ageRef = toRef(obj.value, 'age');
*/

/*
vue3 Proxy 是保证 this 指向问题
参考:https://juejin.cn/post/7222460499493011515
*/

/* computed 使用函数简写时候,是不能修改值的,为 readonly */

/* watch 监听某个具体值要写一个函数返回,ref、reactive 都是如此,
直接监听 reactive 是没有旧值,具体到某个属性是有返回旧值,
第三个参数 flush,'pre' | 'post' | 'sync' // 默认:'pre'
*/

vue 样式 BEM

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
// BEM 架构是一种 css 架构,是block、element、modifier的缩写,
// 分别为块层、元素层、修饰符层,element UI 也使用的是这种架构
// 参考:https://juejin.cn/post/7102980936232337445?searchId=20230801141609F99C0AAEFFE49B662D21

$block-sel: "-" !default;
$element-sel: "__" !default;
$modifier-sel: "--" !default;
$namespace:'xm' !default;
@mixin bfc {
height: 100%;
overflow: hidden;
}

//混入
@mixin b($block) {
$B: $namespace + $block-sel + $block; //变量
.#{$B}{ //插值语法#{}
@content; //内容替换
}
}

@mixin flex {
display: flex;
}

@mixin e($element) {
$selector:&;
@at-root {
#{$selector + $element-sel + $element} {
@content;
}
}
}

@mixin m($modifier) {
$selector:&;
@at-root {
#{$selector + $modifier-sel + $modifier} {
@content;
}
}
}
// 使用
@include b('wraps'){
@include bfc;
@include flex;
@include e(right){
flex:1;
display: flex;
flex-direction: column;
}
}

组件语法补充

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
// ref 获取组件 DOM 定义类型
<Menu ref="refMenu"></Menu>

<script setup lang="ts">
import MenuCom from '../xxxxxxx.vue'
// 注意这儿的typeof里面放的是组件名字 (MenuCom) 不是 ref 的名字 ref 的名字对应开头的变量名 (refMenu)
const refMenu = ref<InstanceType<typeof MenuCom>>()
</script>

// 动态组件警告,使用 markRaw、shallowRef
const isCom = shallowRef(A)
const tab = reactive<Com[]>([{
name: "A组件",
comName: markRaw(A)
}, {
name: "B组件",
comName: markRaw(B)
}])

// gsap 数字滚动
<div>{{ num.tweenedNumber.toFixed(0) }}</div>

<script setup lang='ts'>
import { reactive, watch } from 'vue'
import gsap from 'gsap'
const num = reactive({
tweenedNumber: 0,
current:0
})

watch(()=>num.current, (newVal) => {
gsap.to(num, {
duration: 1,
tweenedNumber: newVal
})
})

</script>

//
Provide / Inject 设置不能修改
import { inject, provide, ref, readonly } from 'vue'
import type { Ref } from "vue"
provide('flag', readonly(flag)) // 父组件
const flag = inject<Ref<number>>('flag', ref(1)) // 子组件,设置类型与默认值

// mitt 补充
import mitt from 'mitt'
const emitter = mitt()
emitter.on('*', (type, e) => console.log(type, e) ) // 监听所有事件
emitter.all.clear() // 清除所有事件

vue3 tsx 语法

参考:小满 csdn vue3 文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 方式 1 直接返回一个渲染函数


const renderDom = () => {
return (<div></div>)
}

export default renderDom
// 方式 2 使用 defineComponent vue3 的 setup
import { defineComponent } from "vue";
export default defineComponent({
setup(){
return ()=> (<div></div>)
}
})
// 方式 3 使用 defineComponent 的 vue2 写法
import { defineComponent } from "vue";
export default defineComponent({
data() {},
render() {
return (<div></div>)
}
})

指令补充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// v-model 自定义修饰符
<Child v-model.is:title="xxx"></Chlid> // 父组件
// 子组件
type Props = {
title?: string,

titleModifiers?: {
is: boolean
}

}

const propData = defineProps<Props>()


const close = () => {
console.log(propData.titleModifiers.is);

}

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
// mian.ts ,添加全局属性或函数
const app = createApp({})
app.config.globalProperties.$http = () => {}
// template 里面可以直接使用,setup 里面的两种使用方式
// 1.第一种方式
import { getCurrentInstance, ComponentInternalInstance } from 'vue';

const { appContext } = <ComponentInternalInstance>getCurrentInstance()

console.log(appContext.config.globalProperties.$env);

// 2.第二种方式
(推荐)
import {ref,reactive,getCurrentInstance} from 'vue'
const app = getCurrentInstance()
console.log(app?.proxy?.$filters.format('js'))

// 注意报错声明文件

// 声明要扩充@vue/runtime-core包的声明.
// 这里扩充"ComponentCustomProperties"接口, 因为他是vue3中实例的属性的类型.
declare module 'vue' {
export interface ComponentCustomProperties {
...
}
}

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
<template>
<div v-if="isShow" class="loading">Loading...</div>
</template>

<script setup lang='ts'>
import { ref } from 'vue';
const isShow = ref(false)//定位loading 的开关


//对外暴露 当前组件的属性和方法
defineExpose({isShow})
</script>



import { createVNode, render, VNode, App } from 'vue';
import Loading from './index.vue'

export default {
install(app: App) {
//createVNode vue提供的底层方法 可以给我们组件创建一个虚拟DOM 也就是Vnode
const vnode: VNode = createVNode(Loading)
//render 把我们的Vnode 生成真实DOM 并且挂载到指定节点
render(vnode, document.body)
// Vue 提供的全局配置 可以自定义
app.config.globalProperties.$loading = {
isShow: () => vnode.component?.exposed?.isShow,
}

}
}

//编写声明文件放置报错 和 智能提示
declare module '@vue/runtime-core' {
export interface ComponentCustomProperties {
$loading: {isShow: boolean}
}
}

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// :deep()
// 使用后,会改变属性选择器的位置,
// 如:.a .b 、.a :deep(.b) --> .a .b[data-v-xxx] 、.a[data-v-xxx] .b

// 插槽选择器,在组件里面写上,也就是放 slot 的组件,不写就是默认是父的
<style scoped>
:slotted(.a) {
color:red
}
</style>

// 全局选择器
// 加入全局样式通常都是新建一个 style 标签 不加 scoped 现在有更优雅的解决方案
<style lang="less" scoped>
:global(div){
color:red
}
</style>

// 动态 CSS
对象需要添加引号
<script lang="ts" setup>
import { ref } from "vue"
const red = ref({ color:'pink' })
</script>

<style lang="less" scoped>
.div {
color: v-bind('red.color');
}
</style>

// css module
<template>
<div :class="$style.red">
dws
</div>
</template>

<style module>
.red {
color: red;
font-size: 20px;
}
</style>
// 自定义名称,与组合式 API 使用
<template>
<div :class="[zs.red,zs.border]">
小满是个弟弟
</div>
</template>


<script setup lang="ts">
import { useCssModule } from 'vue'
const css = useCssModule('zs')
</script>

<style module="zs">
.red {
color: red;
font-size: 20px;
}
.border{
border: 1px solid #ccc;
}
</style>

Vue3集成Tailwind CSS

参考:csdn tailwindcss


Vue3集成 unocss原子化

参考:csdn


环境变量

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
// 1. 注意:不能使用动态的后缀,会在生产环境无效
let xxx = 'BASE.URL'
import.meta.env[xxx]

// 2.vite.config.ts 里面使用环境变量的值
import { fileURLToPath, URL } from 'node:url'

import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'



// https://vitejs.dev/config/
export default ({mode}:any) => {

console.log(loadEnv(mode,process.cwd()))

return defineConfig({
plugins: [vue(), vueJsx()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
}

pinia 持久化插件

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
// $subscribe 只要有state 的变化就会走这个函数
TestStore.$subscribe((args,state)=>{
console.log(args,state);

})
// 如果你的组件卸载之后还想继续调用请设置第二个参数
TestStore.$subscribe((args,state)=>{
console.log(args,state);

},{
detached:true,
// ... 其他参数和 watch 类似
})
// 订阅 Actions的调用,只要有 actions 被调用就会走这个函数
Test.$onAction((args)=>{
console.log(args);

})
// 第二个参数组件卸载之后还想继续调用
Test.$onAction((args)=>{
console.log(args);

}, true)


// pinia 持久化插件
// 地址:https://prazdevs.github.io/pinia-plugin-persistedstate/zh/
// yarn add pinia-plugin-persistedstate
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

// 使用
import { defineStore } from 'pinia'

export const useStore = defineStore('main', {
state: () => {
return {
someState: '你好 pinia',
}
},
persist: true, // 此处配置即可,另外也支持 setup 写法
})

router 补充

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
// 命名视图
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'


const routes: Array<RouteRecordRaw> = [
{
path: "/",
components: {
default: () => import('../components/layout/menu.vue'),
header: () => import('../components/layout/header.vue'),
content: () => import('../components/layout/content.vue'),
}
},
]

const router = createRouter({
history: createWebHistory(),
routes
})

export default router

// 使用
<router-view></router-view>
<router-view name="header"></router-view>
<router-view name="content"></router-view>


// 重定向-别名
// 1. 字符串写法
redirect:'/user1',
// 2. 对象形式配置
redirect: { path: '/user1' },
// 3. 函数模式(可以传参)
redirect: (to) => {
return {
path: '/user1',
query: to.query
}
}

// 别名 alias
alias:["/root","/root2","/root3"],