Vue3 学习

文档

文档说明

  1. 学习地址
  2. 快速上手
  3. vue 中文官网
  4. 代码测试
  5. Vue3 掘金

开始

创建

文档

使用 vue-cli 创建

1
2
3
$ npm install - g @vue / cli # # 安装或者升级
$ vue--version # # 保证 vue cli 版本在 4.5 .0 以上 当前安装版本5 .0 .4
$ vue create my - project # # 创建项目

安装步骤
注意: 选项无法按箭头键选择时, 可以用数字键选择

  • Use https://registry.npmmirror.com for faster installation? Yes 使用更快的 npm 镜像
  • Target directory D:\桌面\github\vue-3.0 already exists. Pick an action: (Use
    arrow keys) ** merge 文件已存在, 合并本地文件
  • ? Please pick a preset: Default ([Vue 3] babel, eslint)**1 使用 vue3 配置项(一般选择自定义配置)
  • ? Pick the package manager to use when installing dependencies: Yarn 安装

创建成功之后一般会自动安装依赖包

1
2
$ cd vue - 3.0
$ yarn serve

使用 vite 创建

文档

  • vite 是一个由原生 ESM 驱动的 Web 开发构建工具。在开发环境下基于浏览器原生 ES imports 开发

  • 它做到了**本地快速开发启动**, 在生产环境下基于 Rollup 打包

    1. 快速的冷启动,不需要等待打包操作;
    1. 即时的热模块更新,替换性能和模块数量的解耦让更新飞起
    1. 真正的按需编译,不再等待整个应用编译完成,这是一个巨大的改变。
1
2
3
4
$ npm init vite - app < project - name >
$ cd < project - name >
$ npm install 或者 yarn
$ npm run dev 或者 yarn dev

Composition API

setup

  • 新的 option, 所有的组合 API 函数都在此使用, 只在初始化时执行一次
  • 函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用

注意: setup 细节问题

  • setup 是在 beforeCreate 生命周期回调之前就执行了, 而且就执行一次

    1. 由此可以推断出:setup 在执行的时候, 当前的组件还没有创建出来, 也就意味着: 组件实例对象 this 根本就不能用
  • 2.this 是 undefined, 说明, 就不能通过 this 再去调用 data/computed/methods/props 中的相关内容了

    1. 其实所有的 composition API 相关回调函数中也都不可以
  • setup 中的返回值是一个对象, 内部的属性和方法是给 html 模版使用的

  • setup 中的对象内部的属性和 data 函数中的 return 对象的属性都可以在 html 模版中使用

  • setup 中的对象中的属性和 data 函数中的对象中的属性会合并为组件对象的属性

  • setup 中的对象中的方法和 methods 对象中的方法会合并为组件对象的方法

  • 在 Vue3 中尽量不要混合的使用 data 和 setup 及 methods 和 setup

  • 一般不要混合使用: methods 中可以访问 setup 提供的属性和方法, 但在 setup 方法中不能访问 data 和 methods

  • setup 不能是一个 async 函数: 因为返回值不再是 return 的对象, 而是 promise, 模板看不到 return 对象中的属性数据

  • setup 的参数

    • setup(props, context) / setup(props, {attrs, slots, emit})
    • props: 包含 props 配置声明且传入了的所有属性的对象
    • attrs: 包含没有在 props 配置中声明的属性的对象, 相当于 this.$attrs
    • slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots
    • emit: 用来分发自定义事件的函数, 相当于 this.$emit
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
<template>
<h2>Child子级组件</h2>
<h3>msg:{{ msg }}</h3>
<!-- <h3>count:{{ count }}</h3> -->
<button @click="emitXxx">分发事件</button>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "Child",
props: ["msg"],
emits: ["fn"], // 可选的, 声明了更利于程序员阅读, 且可以对分发的事件数据进行校验
// beforeCreate() {
// console.log('beforeCreate执行了')
// },
// 界面渲染完毕
// mounted() {},
// setup(props,context) {
setup(props, { attrs, slots, emit }) {
// props参数,是一个对象,里面有父级组件向子级组件传递的数据,并且是在子级组件中使用props接收到的所有的属性
// 包含props配置声明且传入了的所有属性的对象
// console.log(props.msg)
// console.log(context.attrs)
// console.log(context.emit)
// context参数,是一个对象,里面有attrs对象(获取当前组件标签上的所有的属性的对象,但是该属性是在props中没有声明接收的所有的尚需经的对象),emit方法(分发事件的),slots对象(插槽)
// 包含没有在props配置中声明的属性的对象, 相当于 this.$attrs
// console.log(context.attrs.msg2)
// console.log('=============')
console.log("setup执行了", this);

const showMsg1 = () => {
console.log("setup中的showMsg1方法");
};
// 按钮的点击事件的回调函数
function emitXxx() {
// context.emit('xxx','++')
emit("fn", "++");
}
return {
showMsg1,
emitXxx
// setup中一般都是返回一个对象,对象中的属性和方法都可以在html模版中直接使用
};
}
// data() {
// return {
// count: 10,
// }
// },
// // 界面渲染后的生命周期回调
// mounted() {
// console.log(this)
// },
// // 方法的
// methods: {
// showMsg2() {
// console.log('methods中的showMsg方法')
// },
// },
});
</script>

ref

  • 作用: 定义一个数据的响应式
  • 语法: const xxx = ref(initValue):
    • 创建一个包含响应式数据的引用(reference)对象
    • js 中操作数据: xxx.value
    • 模板中操作数据: 不需要.value
  • 一般用来定义一个基本类型的响应式数据

更新页面数量

1
2
3
4
5
<template>
<h2>setup和ref的基本使用</h2>
<h3>{{ count }}</h3>
<button @click="updateCount">更新数据</button>
</template>
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
< script lang = "ts" >
import {
defineComponent,
ref
} from "vue";
export default defineComponent({
name: "App",
// 需求:页面打开后可以直接看到一个数据,点击按钮后,该数据可以发生变化
// vue2的方式实现
// data() {
// return {
// count: 0, // 属性
// }
// },
// methods: {
// updateCount() { // 方法
// this.count++
// },
// },
// vue3的方式实现
// setup是组合API的入口函数
// 初始化
setup() {
// console.log('第一次')
// 变量
// let count = 0 // 此时的数据并不是响应式的数据(响应式数据:数据变化,页面跟着渲染变化)
// ref是一个函数,作用:定义一个响应式的数据,返回的是一个Ref对象,对象中有一个value属性,如果需要对数据进行操作,需要使用该Ref对象调用value属性的方式进行数据的操作
// html模版中是不需要使用.value属性的写法
// 一般用来定义一个基本类型的响应式数据
// count 的类型 Ref类型
const count = ref(0);
console.log(count);
// 方法
function updateCount() {
console.log("=====");
// 报错的原因:count是一个Ref对象,对象是不能进行++的操作
// count++
count.value++;
}
// 返回的是一个对象
return {
// 属性
count,
// 方法
updateCount,
};
},
}); <
/script>

reactive

  • 作用: 定义多个数据的响应式
  • const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
  • 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
1
2
3
4
5
6
7
8
9
<template>
<h2>reactive的使用</h2>
<h3>名字:{{ user.name }}</h3>
<h3>年龄:{{ user.age }}</h3>
<h3>性别:{{ user.gender }}</h3>
<h3>媳妇:{{ user.wife }}</h3>
<hr />
<button @click="updateUser">更新数据</button>
</template>
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
// 需求:显示用户的相关信息,点击按钮,可以更新用户的相关信息数据 /* reactive 作用:
定义多个数据的响应式
const proxy = reactive(obj):
接收一个普通对象然后返回该普通对象的响应式代理器对象
响应式转换是“ 深层的”: 会影响对象内部所有嵌套的属性 内部基于 ES6 的 Proxy
实现, 通过代理对象操作源对象内部数据都是响应式的 * / <
script lang = "ts" >
import {
defineComponent,
reactive
} from "vue";
export default defineComponent({
name: "App",
setup() {
const obj = {
name: "小明",
age: 20,
wife: {
name: "小甜甜",
age: 18,
cars: ["奔驰", "宝马", "奥迪"],
},
};
// 把数据变成响应式的数据
// 返回的是一个Proxy的代理对象,被代理的目标对象就是obj对象
// user现在是代理对象,obj是目标对象
// user对象的类型是Proxy
const user = reactive < any > (obj); //Proxy对象
console.log(user);
const updateUser = () => {
//只能使用代理对象的方式来更新数据(响应式数据)
user.name = "小工";
user.wife.cars[3] = "奥拓";
};
return {
updateUser,
user,
};
},
}); <
/script>

响应式的原理

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
<template>
<h2>reactive的使用</h2>
<h3>名字:{{ proxyUser.name }}</h3>
<h3>年龄:{{ proxyUser.age }}</h3>
<h3>性别:{{ proxyUser.gender }}</h3>
<h3>媳妇:{{ proxyUser.wife }}</h3>
<hr />
<button @click="updateUser">更新数据</button>
</template>
<script lang="ts">
import { defineComponent, reactive } from "vue";
export default defineComponent({
name: "App",
setup() {
//定义接口
interface User {
name: string;
age: number;
wife: {
name?: string;
age: number;
};
gender?: string;
//[proppName:string]:any;
}
// 目标对象
const user: User = {
name: "佐助",
age: 20,
wife: {
name: "小樱",
age: 19
}
};
// 把目标对象变成代理对象
// 参数1:user---->target目标对象
// 参数2:handler---->处理器对象,用来监视数据,及数据的操
const proxyUser = new Proxy(user, {
// 获取目标对象的某个属性值
get(target, prop) {
console.log("get方法调用了");
return Reflect.get(target, prop);
},
// 修改目标对象的属性值/为目标对象添加新的属性
set(target, prop, val) {
console.log("set方法调用了");
return Reflect.set(target, prop, val);
},
// 删除目标对象上的某个属性
deleteProperty(target, prop) {
console.log("delete方法调用了");
return Reflect.deleteProperty(target, prop);
}
});
//页面不会更新响应式数据
const updateUser = () => {
console.log(proxyUser.name);
// 通过代理对象更新目标对象上的某个属性值
proxyUser.name = "鸣人";
console.log(proxyUser);
// 通过代理对象向目标对象中添加一个新的属性
proxyUser.gender = "男";
console.log(user);
delete proxyUser.gender;
// 更新目标对象中的某个属性对象中的属性值
proxyUser.wife.name = "雏田";
console.log(user);
};
return {
proxyUser,
updateUser
};
}
});
</script>

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<template>
<h2>reactive和ref的细节问题</h2>
<h3>m1:{{ m1 }}</h3>
<h3>m2:{{ m2 }}</h3>
<h3>m3:{{ m3 }}</h3>
<hr />
<button @click="update">更新数据</button>
</template>
<script lang="ts">
import { defineComponent, ref, reactive } from "vue";
export default defineComponent({
name: "App",
// 是Vue3的 composition API中2个最重要的响应式API(ref和reactive)
// ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式)
// 如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象
// ref内部: 通过给value属性添加getter/setter来实现对数据的劫持
// reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据
// ref的数据操作: 在js中要.value, 在模板中不需要(内部解析模板时会自动添加.value)
setup() {
// 通过ref的方式设置的数据
const m1 = ref("abc");
const m2 = reactive({
name: "小明",
wife: {
name: "小红"
}
});
// ref也可以传入对象吗
const m3 = ref({
name: "小明",
wife: {
name: "小红"
}
});
// 更新数据
const update = () => {
// ref中如果放入的是一个对象,那么是经过了reactive的处理,形成了一个Proxy类型的对象
console.log(m3);
m1.value += "===";
m2.wife.name += "===";
// m3.value.name += '==='
m3.value.wife.name += "===";
console.log(m3.value.wife);
};
return {
m1,
m2,
m3,
update
};
}
});
</script>

watch 和 watchEffect

  • computed 函数:

    • 与 computed 配置功能一致
    • 只有 getter
    • 有 getter 和 setter
  • watch 函数

    • 与 watch 配置功能一致
    • 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
    • 默认初始时不执行回调, 但可以通过配置 immediate 为 true, 来指定初始时立即执行第一次
    • 通过配置 deep 为 true, 来指定深度监视
  • watchEffect 函数

    • 不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
    • 默认初始时就会执行第一次, 从而可以收集需要监视的数据
    • 监视数据发生变化时回调
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
<template>
<h2>计算属性和监视</h2>
<fieldset>
<legend>姓名操作</legend>
姓氏:<input
type="text"
placeholder="请输入姓氏"
v-model="user.firstName"
/><br />
名字:<input
type="text"
placeholder="请输入名字"
v-model="user.lastName"
/><br />
</fieldset>
<fieldset>
<legend>计算属性和监视的演示</legend>
姓名1:<input
type="text"
placeholder="显示姓名1"
v-model="fullName1"
/><br />
姓名2:<input
type="text"
placeholder="显示姓名2"
v-model="fullName2"
/><br />
姓名3:<input
type="text"
placeholder="显示姓名3"
v-model="fullName3"
/><br />
</fieldset>
</template>
<script lang="ts">
import {
defineComponent,
reactive,
computed,
watch,
ref,
watchEffect
} from "vue";
export default defineComponent({
name: "App",
setup() {
// 定义一个响应式对象
const user = reactive({
// 姓氏
firstName: "东方",
// 名字
lastName: "不败"
});
// 通过计算属性的方式,实现第一个姓名的显示
// vue3中的计算属性
// 计算属性的函数中如果只传入一个回调函数,表示的是get

// 第一个姓名:
// 返回的是一个Ref类型的对象
const fullName1 = computed(() => {
return user.firstName + "_" + user.lastName;
});
// 第二个姓名:
const fullName2 = computed({
get() {
return user.firstName + "_" + user.lastName;
},
set(val: string) {
// console.log('=====',val)
const names = val.split("_");
user.firstName = names[0];
user.lastName = names[1];
}
});

// 第三个姓名:
const fullName3 = ref("");
// 监视----监视指定的数据
watch(
user,
({ firstName, lastName }) => {
fullName3.value = firstName + "_" + lastName;
},
{
immediate: true,
deep: true
}
);
// immediate 默认会执行一次watch,deep 深度监视

// 监视,不需要配置immediate,本身默认就会进行监视,(默认执行一次)
// watchEffect(() => {
// fullName3.value = user.firstName + '_' + user.lastName
// })

// 监视fullName3的数据,改变firstName和lastName
watchEffect(() => {
const names = fullName3.value.split("_");
user.firstName = names[0];
user.lastName = names[1];
});

// watch---可以监视多个数据的
// watch([user.firstName,user.lastName,fullName3],()=>{
// // 这里的代码就没有执行,fullName3是响应式的数据,但是,user.firstName,user.lastName不是响应式的数据
// console.log('====')
// })
// 当我们使用watch监视非响应式的数据的时候,代码需要改一下
watch([() => user.firstName, () => user.lastName, fullName3], () => {
// 这里的代码就没有执行,fullName3是响应式的数据,但是,user.firstName,user.lastName不是响应式的数据
console.log("====");
});

return {
user,
fullName1,
fullName2,
fullName3
};
}
});
</script>

生命周期

生命周期 svg

app = Vue.createApp(options) app.mount(el) Init events & lifecycle beforeCreate Init injections & reactivity created Has “template” option? YES NO Compile template into render function * Compile el’s innerHTML as template * beforeMount Create app.$el and append it to el mounted beforeUpdate updated Virtual DOM re-rendered and patch when data changes Mounted when app.unmount() is called beforeUnmount unmounted Unmounted * Template compilation is performed ahead-of-time if using a build step, e.g., with single-file components.

生命周期

生命周期

与 2.x 版本生命周期相对应的组合式 API

  • beforeCreate -> 使用 setup()
  • created -> 使用 setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • errorCaptured -> onErrorCaptured

新增的钩子函数

组合式 API 还提供了以下调试钩子函数:

  • onRenderTracked
  • onRenderTriggered
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
<template>
<h2>Child子级组件</h2>
<h4>msg:{{ msg }}</h4>
<button @click="update">更新数据</button>
</template>
<script lang="ts">
import {
defineComponent,
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from "vue";
export default defineComponent({
name: "Child",
// vue2.x中的生命周期钩子
beforeCreate() {
console.log("2.x中的beforeCreate...");
},
created() {
console.log("2.x中的created...");
},
beforeMount() {
console.log("2.x中的beforeMount...");
},
mounted() {
console.log("2.x中的mounted...");
},
beforeUpdate() {
console.log("2.x中的beforeUpdate...");
},
updated() {
console.log("2.x中的updated...");
},
// vue2.x中的beforeDestroy和destroyed这两个生命周期回调已经在vue3中改名了,所以,不能再使用了
beforeUnmount() {
console.log("2.x中的beforeUnmount...");
},
unmounted() {
console.log("2.x中的unmounted...");
},

setup() {
console.log("3.0中的setup");
// 响应式的数据
const msg = ref("abc");
// 按钮点击事件的回调
const update = () => {
msg.value += "===";
};
onBeforeMount(() => {
console.log("3.0中的onBeforeMount");
});
onMounted(() => {
console.log("3.0中的onMounted");
});
onBeforeUpdate(() => {
console.log("3.0中的onBeforeUpdate");
});
onUpdated(() => {
console.log("3.0中的onUpdated");
});
onBeforeUnmount(() => {
console.log("3.0中的onBeforeUnmount");
});
onUnmounted(() => {
console.log("3.0中的onUnmounted");
});
return {
msg,
update
};
}
});
</script>

hooks

自定义 hook 函数

  • 使用 Vue3 的组合 API 封装的可复用的功能函数

  • 自定义 hook 的作用类似于 vue2 中的 mixin 技术

  • 自定义 Hook 的优势: 很清楚复用功能代码的来源, 更清楚易懂

  • 需求 1: 收集用户鼠标点击的页面坐标

  • 收集用户鼠标点击的页面坐标

useMousePosition.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { onBeforeUnmount, onMounted, ref } from "vue";
export default function () {
const x = ref(-1);
const y = ref(-1);

// 点击事件的回调函数
const clickHandler = (event: MouseEvent) => {
x.value = event.pageX;
y.value = event.pageY;
};
// 页面已经加载完毕了,再进行点击的操作
// 页面加载完毕的生命周期组合API
onMounted(() => {
window.addEventListener("click", clickHandler);
});
// 页面卸载之前的生命周期组合API
onBeforeUnmount(() => {
window.removeEventListener("click", clickHandler);
});
return {
x,
y
};
}
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
<template>
<div>
<h1>鼠标点击获取坐标</h1>
<h2>x:{{ x }},y:{{ y }}</h2>
<h2>请求接口数据</h2>
<h3 v-if="loading">正在加载中....</h3>
<h3 v-else-if="errorMsg">错误信息:{{ errorMsg }}</h3>
<ul v-else>
<li>id:{{ addressData.id }}</li>
<li>address:{{ addressData.address }}</li>
<li>distance:{{ addressData.distance }}</li>
</ul>
<hr />
<!--数组数据-->
<ul v-for="item in data" :key="item.id">
<li>id:{{ item.id }}</li>
<li>title:{{ item.title }}</li>
<li>price:{{ item.price }}</li>
</ul>
</div>
</template>
<script lang="ts">
import { defineComponent, watch } from "vue";
import useMousePosition from "./useMousePosition";
import useRequest from "./useRequest";
// 定义接口,约束对象的类型
interface AddressData {
id: number;
address: string;
distance: string;
}
interface ProductsData {
id: string;
title: string;
price: number;
}
export default defineComponent({
setup() {
//1.鼠标坐标
const { x, y } = useMousePosition();
//2.自定义数据获取
// 发送请求
// 对象
const { data: addressData } =
useRequest<AddressData>("/data/address.json"); // 获取对象数据
// 数组对象
const { loading, data, errorMsg } = useRequest<ProductsData[]>(
"/data/products.json"
); // 获取数组数据
// 监视
watch(data, () => {
if (data.value) {
console.log(data.value.length);
}
});
return {
x,
y,
loading,
data,
addressData,
errorMsg
};
}
});
</script>
  • 利用 TS 泛型强化类型检查

  • 需求 2: 封装发 ajax 请求的 hook 函数

    useRequest.ts

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
import { ref } from "vue";
// 引入axios
import axios from "axios";
// 发送ajax的请求
export default function <T>(url: string) {
// 加载的状态
const loading = ref(true);
// 请求成功的数据
const data = (ref < T) | (null > null); // 坑
// 错误信息
const errorMsg = ref("");
// 发送请求
axios
.get(url)
.then((response) => {
// 改变加载状态
loading.value = false;
data.value = response.data;
})
.catch((error) => {
// 改变加载状态
loading.value = false;
errorMsg.value = error.message || "未知错误";
});
return {
loading,
data,
errorMsg
};
}

toRefs

  • 把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
  • 应用: 当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
  • 问题: reactive 对象取出的所有属性值都是非响应式的
  • 解决: 利用 toRefs 可以将一个响应式 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<template>
<h2>toRefs的使用</h2>
<!-- <h3>name:{{ state.name }}</h3>
<h3>age:{{ state.age }}</h3> -->

<h3>name:{{ name }}</h3>
<h3>age:{{ age }}</h3>

<h3>name2:{{ name2 }}</h3>
<h3>age2:{{ age2 }}</h3>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from "vue";

function useFeatureX() {
const state = reactive({
name2: "自来也",
age2: 47
});
return {
...toRefs(state)
};
}
export default defineComponent({
name: "App",
setup() {
const state = reactive({
name: "自来也",
age: 47
});
// toRefs可以把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
// const state2 = toRefs(state)
const { name, age } = toRefs(state);
// console.log(state2)
// 定时器,更新数据,(如果数据变化了,界面也会随之变化,肯定是响应式的数据)
setInterval(() => {
// state.name += '=='
// state2.name.value+='==='
name.value += "===";
console.log("======");
}, 1000);

const { name2, age2 } = useFeatureX();
return {
// state,
// 下面的方式不行啊
// ...state // 不是响应式的数据了---->{name:'自来也',age:47}
// ...state2 toRefs返回来的对象
name,
age,
name2,
age2
};
}
});
</script>

ref 获取元素

利用 ref 函数获取组件中的标签元素

功能需求: 让输入框自动获取焦点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<h2>ref的另一个作用:可以获取页面中的元素</h2>
<input type="text" ref="inputRef" />
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from "vue";
export default defineComponent({
name: "App",
// 需求:当页面加载完毕后,页面中的文本框可以直接获取焦点(自动获取焦点)
setup() {
// 默认是空的,页面加载完毕,说明组件已经存在了,获取文本框元素
const inputRef = ref<HTMLElement | null>(null);
// 页面加载后的生命周期组合API
onMounted(() => {
inputRef.value && inputRef.value.focus(); // 自动获取焦点
});
return {
inputRef
};
}
});
</script>

shallowReactive 与 shallowRef

  • shallowReactive : 只处理了对象内最外层属性的响应式(也就是浅响应式)

  • shallowRef: 只处理了 value 的响应式, 不进行对象的 reactive 处理

  • 什么时候用浅响应式呢?

    • 一般情况下使用 ref 和 reactive 即可
    • 如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> shallowReactive
    • 如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef

shallowReactive 与 shallowRef
shallowReactive: 只处理了对象内最外层属性的响应式(也就是浅响应式)
shallowRef: 只处理了 value 的响应式, 不进行对象的 reactive 处理
总结:
reactive 与 ref 实现的是深度响应式, 而 shallowReactive 与 shallowRef 是浅响应式
什么时候用浅响应式呢?
一般情况下使用 ref 和 reactive 即可,
如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> 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
49
50
51
52
53
54
55
56
57
<template>
<h2>App</h2>

<h3>m1: {{m1}}</h3>
<h3>m2: {{m2}}</h3>
<h3>m3: {{m3}}</h3>
<h3>m4: {{m4}}</h3>

<button @click="update">更新</button>
</template>

<script lang="ts">
import { reactive, ref, shallowReactive, shallowRef } from "vue";
export default {
setup() {
const m1 = reactive({
a: 1,
b: {
c: 2
}
});
const m2 = shallowReactive({
a: 1,
b: {
c: 2
}
});
const m3 = ref({
a: 1,
b: {
c: 2
}
});
const m4 = shallowRef({
a: 1,
b: {
c: 2
}
});
const update = () => {
// m1.b.c += 1
// m2.b.c += 1

// m3.value.a += 1
m4.value.a += 1; //不会更新视图
};

return {
m1,
m2,
m3,
m4,
update
};
}
};
</script>

readonly 与 shallowReadonly

  • readonly:
    • 深度只读数据
    • 获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
    • 只读代理是深层的:访问的任何嵌套 property 也是只读的。
  • shallowReadonly
    • 浅只读数据
    • 创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
  • 应用场景:
    • 在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除
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>
<h2>readonly和shallowReadonly</h2>
<h3>state:{{ state2 }}</h3>
<hr />
<button @click="update">更新数据</button>
</template>
<script lang="ts">
import { defineComponent, reactive, readonly, shallowReadonly } from "vue";
export default defineComponent({
name: "App",
setup() {
const state = reactive({
name: "佐助",
age: 20,
car: {
name: "奔驰",
color: "yellow"
}
});
// 只读的数据---深度的只读
// const state2 = readonly(state)
// 只读的数据---浅只读的
const state2 = shallowReadonly(state);
const update = () => {
// state2.name += '==='//不能操作
// state2.car.name += '==' //不能操作
// state2.name+='===' // 不能操作
state2.car.name += "==="; //可以更新视图
};
return {
state2,
update
};
}
});
</script>

toRaw 与 markRaw

  • toRaw
    • 返回由 reactivereadonly 方法转换成响应式代理的普通对象。
    • 这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
  • markRaw
    • 标记一个对象,使其永远不会转换为代理。返回对象本身
    • 应用场景:
      • 有些值不应被设置为响应式的,例如复杂的第三方类实例或 Vue 组件对象。
      • 当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。
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>
<h2>toRaw和markRaw</h2>
<h3>state:{{ state }}</h3>
<hr />
<button @click="testToRaw">测试toRaw</button>
<button @click="testMarkRaw">测试markRaw</button>
</template>
<script lang="ts">
import { defineComponent, markRaw, reactive, toRaw } from "vue";
interface UserInfo {
name: string;
age: number;
likes?: string[];
}
export default defineComponent({
name: "App",
setup() {
const state = reactive<UserInfo>({
name: "小明",
age: 20
});

const testToRaw = () => {
// 把代理对象变成了普通对象了,数据变化,界面不变化
const user = toRaw(state);
user.name += "==";
console.log("哈哈,我好帅哦");
};
const testMarkRaw = () => {
// state.likes = ['吃', '喝']
// state.likes[0] += '=='
// console.log(state)
const likes = ["吃", "喝"];
// markRaw标记的对象数据,从此以后都不能再成为代理对象了
state.likes = markRaw(likes);
//不会更新视图
setInterval(() => {
if (state.likes) {
state.likes[0] += "=";
console.log("定时器走起来");
}
}, 1000);
};
return {
state,
testToRaw,
testMarkRaw
};
}
});
</script>

customRef

  • 创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
  • 需求: 使用 customRef 实现 debounce 的示例
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
<template>
<h2>CustomRef的使用</h2>
<input type="text" v-model="keyword" />
<p>{{ keyword }}</p>
</template>
<script lang="ts">
import { customRef, defineComponent, ref } from "vue";
// 自定义hook防抖的函数
// value传入的数据,将来数据的类型不确定,所以,用泛型,delay防抖的间隔时间.默认是200毫秒
function useDebouncedRef<T>(value: T, delay = 200) {
// 准备一个存储定时器的id的变量
let timeOutId: number | any;
return customRef((track, trigger) => {
return {
// 返回数据的
get() {
// 告诉Vue追踪数据
track();
return value;
},
// 设置数据的
set(newValue: T) {
// 清理定时器
clearTimeout(timeOutId);
// 开启定时器
timeOutId = setTimeout(() => {
value = newValue;
// 告诉Vue更新界面
trigger();
}, delay);
}
};
});
}
export default defineComponent({
name: "App",
setup() {
// const keyword = ref('abc')
const keyword = useDebouncedRef("abc", 500);
return {
keyword
};
}
});
</script>

toRef(子父组件通讯)

  • 为源响应式对象上的某个属性创建一个 ref 对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
  • 区别 ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
  • 应用: 当要将 某个 prop 的 ref 传递给复合函数时,toRef 很有用

Child 子级组件

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
<template>
<h2>Child子级组件</h2>
<h3>age:{{ age }}</h3>
<h3>length:{{ length }}</h3>
</template>
<script lang="ts">
import { defineComponent, computed, Ref, toRef } from "vue";

function useGetLength(age: Ref) {
return computed(() => {
return age.value.toString().length;
});
}
export default defineComponent({
name: "Child",
props: {
age: {
type: Number,
required: true // 必须的
}
},
setup(props) {
const length = useGetLength(toRef(props, "age"));
return {
length
};
}
});
</script>

父组件

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
<template>
<h2>toRef的使用及特点:</h2>
<h3>state:{{ state }}</h3>
<h3>age:{{ age }}</h3>
<h3>money:{{ money }}</h3>
<hr />
<button @click="update">更新数据</button>
<hr />
<Child :age="age" />
</template>
<script lang="ts">
import { defineComponent, reactive, toRef, ref } from "vue";
import Child from "./child.vue";
export default defineComponent({
name: "App",
components: {
Child
},
setup() {
const state = reactive({
age: 5,
money: 100
});
// 把响应式数据state对象中的某个属性age变成了ref对象了
const age = toRef(state, "age"); //子父组件公用数据
// 把响应式对象中的某个属性使用ref进行包装,变成了一个ref对象
const money = ref(state.money);
// console.log(age)
// console.log(money)
const update = () => {
// 更新数据的
// console.log('测试')
state.age += 2; //子父视图更新
//age.value += 3 //子父视图更新
//money.value += 10 //父视图更新
};
return {
state,
age,
money,
update
};
}
});
</script>

customRef

  • 创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
  • 需求: 使用 customRef 实现 debounce 的示例
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
<template>
<h2>CustomRef的使用</h2>
<input type="text" v-model="keyword" />
<p>{{ keyword }}</p>
</template>
<script lang="ts">
import { customRef, defineComponent, ref } from "vue";
// 自定义hook防抖的函数
// value传入的数据,将来数据的类型不确定,所以,用泛型,delay防抖的间隔时间.默认是200毫秒
function useDebouncedRef<T>(value: T, delay = 200) {
// 准备一个存储定时器的id的变量
let timeOutId: number | any;
return customRef((track, trigger) => {
return {
// 返回数据的
get() {
// 告诉Vue追踪数据
track();
return value;
},
// 设置数据的
set(newValue: T) {
// 清理定时器
clearTimeout(timeOutId);
// 开启定时器
timeOutId = setTimeout(() => {
value = newValue;
// 告诉Vue更新界面
trigger();
}, delay);
}
};
});
}
export default defineComponent({
name: "App",
setup() {
// const keyword = ref('abc')
const keyword = useDebouncedRef("abc", 500);
return {
keyword
};
}
});
</script>

provide 与 inject

  • provideinject提供依赖注入,功能类似 2.x 的provide/inject

  • 实现跨层级组件(祖孙)间通信

  • 代码糖

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 {
reactive,
provide
} from "vue";
export default {
name: "parent",
components: {
childTest,
},
setup() {
let person = reactive({
name: "张三",
age: 18,
sex: "男",
});
// 给自己的后代组件传递数据
provide("person", person);

return {
person,
};
},
};

// 后代组件
import {
inject
} from "vue";
export default {
setup() {
// 函数的返回值就是我们所接收的值
let person = inject("person");
return {
person,
};
},
};

响应式数据的判断

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

组件

Fragment(片断)

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

Teleport(瞬移)

  • Teleport 提供了一种干净的方法, 让组件的 html 在父组件界面外的特定标签(很可能是 body)下插入显示

ModalButton.vue

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
<template>
<button @click="modalOpen = true">
Open full screen modal! (With teleport!)
</button>

<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
I'm a teleported modal! (My parent is "body")
<button @click="modalOpen = false">Close</button>
</div>
</div>
</teleport>
</template>

<script>
import { ref } from "vue";
export default {
name: "modal-button",
setup() {
const modalOpen = ref(false);
return {
modalOpen
};
}
};
</script>

<style>
.modal {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

.modal div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: white;
width: 300px;
height: 300px;
padding: 5px;
}
</style>

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<h2>App</h2>
<modal-button></modal-button>
</template>

<script lang="ts">
import ModalButton from "./ModalButton.vue";

export default {
setup() {
return {};
},

components: {
ModalButton
}
};
</script>

Suspense(不确定的)

  • 它们允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验
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
<template>
<Suspense>
<template v-slot:default>
<AsyncComp />
<!-- <AsyncAddress/> -->
</template>

<template v-slot:fallback>
<h1>LOADING...</h1>
</template>
</Suspense>
</template>

<script lang="ts">
/*
异步组件 + Suspense组件
*/
// import AsyncComp from './AsyncComp.vue'
import AsyncAddress from "./AsyncAddress.vue";
import { defineAsyncComponent } from "vue";
const AsyncComp = defineAsyncComponent(() => import("./AsyncComp.vue"));
export default {
setup() {
return {};
},

components: {
AsyncComp,
AsyncAddress
}
};
</script>
  • AsyncComp.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<h2>AsyncComp22</h2>
<p>{{ msg }}</p>
</template>

<script lang="ts">
export default {
name: "AsyncComp",
setup() {
// return new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve({
// msg: 'abc'
// })
// }, 2000)
// })
return {
msg: "abc"
};
}
};
</script>
  • AsyncAddress.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<h2>{{ data }}</h2>
</template>

<script lang="ts">
import axios from "axios";
export default {
async setup() {
const result = await axios.get("/data/address.json");
return {
data: result.data
};
}
};
</script>

createVNode

  • 创建虚拟节点插入到对应元素中
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
<template>
<div ref="here"></div>
</template>

<script lang="ts" setup>
import { render, ref, createVNode, onMounted } from "vue";
// 元素Dom
const here = ref();

// 创建vnode节点
const div = createVNode("div", {
class: ["className"],
style: {
width: "100px",
height: "100px",
border: "1px solid black"
},
onClick: () => ({})
});

onMounted(() => {
// 渲染到对应元素
render(div, here.value);
});
</script>

插槽

1
2
3
4
5
6
7
8
9
10
<!-- 父级 -->
<div v-slot:'插槽名'>我是插槽内容</div>

<!-- 子组件 -->
<template #插槽名>
<slot name="插槽名"></slot>
</template>

<!-- 孙组件 -->
<slot name='插槽名'></slot>

$attrs

1
2
<!-- 父级 -->
<子组件 v-bind="$attrs"></子组件>
1
2
3
4
export default {
name: "父级组件名",
inheritAttrs: true // 继承组件props等属性,
};

路由

步骤

    1. 安装
      npm install vue-router@next --save
    1. 新建组件
      在 components 文件下新建 vue 页面文件
    1. 在 src 目录下新建 router.ts 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 引入vue-router
import { createRouter, createWebHashHistory } from "vue-router";

// 引入组件
import Reactive from "./components/reactive.vue";

const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: "/reactive",
component: Reactive
}
]
});
// 导出router
export default router;

注意

找不到模块“xxx.vue”或其相应的类型声明问题解决
在项目根目录或 src 文件夹下创建一个后缀为 .d.ts 的文件,并写入以下内容:
在 src 新建 vue.d.ts 文件

1
2
3
4
5
6
7
8
9
10
declare module '*.vue' {
import {
App,
defineComponent
} from 'vue'
const component: ReturnType < typeof defineComponent > & {
install(app: App): void
}
export default component
}
    1. 在入口文件 main.ts 中挂载使用 router
1
2
3
4
5
6
7
8
import { createApp } from "vue";
import App from "./App.vue";
import "./index.css";
import router from "./router"; // 引入router
// createApp(App).mount('#app')
const app = createApp(App);
app.use(router); //挂载使用router
app.mount("#app");
    1. 在跟组件 App.vue 中使用路由
  • 视图

1
<router-view></router-view>
  • 跳转
1
<router-link to="/reative">reative</router-link>

其他

导入 axios 时,报错

Uncaught SyntaxError: The requested module ‘/@modules/axios/index.js’ does not provide an export named ‘default’

方法一
处理: 下载 axios 的位置必须在”dependencies”中而不能是 “devDependencies”

方法二

  1. 将 vite 升级到了 vite2.x 版本
  2. 对应插件@vitejs/plugin-vue

package.json

1
2
3
4
5
"devDependencies": {
"@vue/compiler-sfc": "^3.0.4",
"@vitejs/plugin-vue": "^2.3.3",
"vite": "^1.0.0-rc.13"
}

插件配置
根目录新建 vite.config.js

1
2
3
4
const vue = require("@vitejs/plugin-vue");
module.exports = {
plugins: [vue()]
};