Vue3常用Composition Api
- 去除eslint语法检查操作:文件 => 首选项 => 设置 => 输入eslint,去除第二个勾选
- setup 【新的 option, 所有的组合 API 函数都在此使用, 只在初始化时执行一次,函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用】
setup和ref的基本使用
ref作用: 定义一个数据的响应式
- 语法: const xxx = ref(initValue):
- 创建一个包含响应式数据的引用(reference)对象
- js 中操作数据: xxx.value
- 模板中操作数据: 不需要.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<template>
<div id="app">
<h1>setup和ref的基本使用</h1>
{{count}}
<button @click='upDataCount'>按钮</button>
</div>
<h3></h3>
</template>
<script lang='ts'>
import { defineComponent,ref } from "vue";
export default defineComponent({
name: "App",
// 需求:打开页面有一个数组,点击按钮,数组改变
// setup是组合API的入口函数
setup() {
// 变量
// let count = 0 // 并不是响应式的数据(数据变化,页面跟着变化)
// ref是一个函数,作用:定义一个响应式数据,对象中有一个value属性
// html中不需要使用.value属性的
// 一般用来定义一个基本类型的响应式数据
// count的对象类型是Ref
const count = ref(0)
// 方法
function upDataCount(){
// 报错原因:count是一个Ref对象,对象不能进行++操作
// count++
count.value++
}
// 返回的是一个对象
return {
count: count,
upDataCount
}
}
})
</script>reactive : 把复杂数据变成响应式数据
- 作用: 定义多个数据的响应式
- const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
- 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
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<template>
<div id="app">
<h1>reactive的使用</h1>
<h2>姓名:{{user.name}}</h2>
<h2>年龄:{{user.age}}</h2>
<h2>老婆:{{user.wife}}</h2>
<h2>老婆姓名:{{user.wife.name}}</h2>
<h2>性别:{{user.gender}}</h2>
<button @click='upDataCount'>按钮</button>
</div>
<h3></h3>
</template>
<script lang='ts'>
import { defineComponent, reactive } from "vue";
export default defineComponent({
name: "App",
// 需求:显示用户信息,点击按钮,可以更新用户数据
setup() {
// 把复杂数据变成响应式数据
// 接收一个普通对象然后返回该普通对象的响应式代理器对象
// user的对象类型是Proxy
const obj: any = {
name: 'he',
age: 23,
wife: {
name: 'zhang',
age: 22
}
}
const user = reactive < any > (obj)
// 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
// console.log(user);
// 方法
// function upDataCount(){
// user.name = 'zz'
// }
let upDataCount = () => {
// user.name = 'xx'
// user:代理对象 obj:目标对象
// 添加一个属性,那种会影响页面的更新
// obj.gender = '男' // 页面不能更新
// user.gender = '男' // 页面更新
delete user.age // 页面更新
// 删除一个属性,那种会影响页面的更新
console.log(user);
}
return {
user: user,
upDataCount
}
}
})
</script>
比较 Vue2 与 Vue3 的响应式(重要)
Vue2
- 对象: 通过 defineProperty 对对象的已有属性值的读取和修改进行劫持(监视/拦截)
- 数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
Vue2的问题:
- 对象直接新添加的属性或删除已有属性, 界面不会自动更新
- 直接通过下标替换元素或更新 length, 界面不会自动更新 arr[1] = {}
Vue3
- 通过 Proxy(代理): 拦截对 data 任意属性的任意(13 种)操作, 包括属性值的读写, 属性的添加, 属性的删除等
- 通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作
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<template>
<div id="app">
<h1>reactive的使用</h1>
<h2>{{user.name}}</h2>
<h2>{{user.wife}}</h2>
<h2>{{user.wife.name}}</h2>
<button @click='upDataCount'>按钮</button>
</div>
<h3></h3>
</template>
<script type="text/javascript">
import { defineComponent, reactive } from "vue";
export default defineComponent({
name: "App",
setup() {
const user = {
name: 'he',
age: 23,
wife: {
name: 'zhang',
age: 22
}
}
// target: 要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
// handler: 一个通常以函数作为属性的对象,各属性中的函数分别定义了执行各种操作时代理proxy的行为。
const proxy = new Proxy(user, {
// target:目标对象 property:属性名 receiver: Proxy 或者继承Proxy的对象
// 用于拦截对象的读取属性操作
get(target, prop) {
console.log('get调用');
// return Reflect.get(user, "name"); // he
return Reflect.get(target, prop)
},
// 设置属性值操作的器皿
set(target, prop, val) {
console.log('set调用');
return Reflect.set(target, prop, val)
},
// 拦截对对象的delete操作
// 拦截删除属性
deleteProperty(target, prop) {
console.log('deleteProperty调用');
return Reflect.deleteProperty(target, prop)
}
})
// get调用
// console.log(proxy.name);
// set调用
// proxy.name = 'lisi'
// proxy.gender = '男'
// deleteProperty调用
// delete proxy.age
// get调用
// proxy.wife.name = 'wanwu'
delete proxy.wife.name
console.log(user);
console.log(proxy);
// let upDataCount = () => {
// console.log(user);
// }
return {
user: user,
}
}
})
</script>
setup 细节
1.setup 执行的时机
- 在 beforeCreate 之前执行(一次), 此时组件对象还没有创建
- this 是 undefined, 不能通过 this 来访问 data/computed/methods / props
- 其实所有的 composition API 相关回调函数中也都不可以
2.setup 返回值
- 返回一个对象,可以直接使用对象中的属性或方法
- 返回对象中的属性会与 data 函数返回对象的属性合并成为组件对象的属性
- 返回对象中的方法会与 methods 中的方法合并成功组件对象的方法
- setup 不能是一个 async 函数: 因为返回值不再是 return 的对象, 而是 promise, 模板看不到 return 对象中的属性数据
3.setup 参数
- setup(props, context) / setup(props, {attrs, slots, emit})
- props: 包含 props 配置声明且传入了的所有属性的对象
- attrs: 包含没有在 props 配置中声明的属性的对象, 相当于 this.$attrs
- slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots
- emit: 用来分发自定义事件的函数, 相当于 this.$emit
Child.vue
1 | <template> |
App.vue
1 | <template> |
reactive 与 ref-细节
- 是 Vue3 的 composition API 中 2 个最重要的响应式 API
- ref 用来处理基本类型数据, reactive 用来处理对象(递归深度响应式)
- 如果用 ref 对象/数组, 内部会自动将对象/数组转换为 reactive 的代理对象
- ref 内部: 通过给 value 属性添加 getter/setter 来实现对数据的劫持
- reactive 内部: 通过使用 Proxy 来实现对对象内部所有数据的劫持, 并通过 Reflect 操作对象内部数据
- ref 的数据操作: 在 js 中要.value, 在模板中不需要(内部解析模板时会自动添加.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<template>
<h2>reactive 与 ref-细节</h2>
<h3>m1:{{ m1 }}</h3>
<h3>m2:{{ m2 }}</h3>
<h3>m3:{{ m3 }}</h3>
<button @click="handleUpdata">更新数据</button>
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from "vue";
export default defineComponent({
name: "",
// 是 Vue3 的 composition API 中 2 个最重要的响应式 API
// ref中如果放入的是一个对象,会经过 reactive的处理,形成一个Proxy类型的对象
setup(props, { attrs, emit, slots }) {
let m1 = ref(0);
let m2 = reactive({ name: "bai", age: 23, wife: ["xiao", "li", "zhao"] });
let m3 = ref({
name: "he",
age: 23,
wife: ["xiao", "li", "zhao"],
});
function handleUpdata() {
// m1.value = 13
// m2.age = 12
// m2.wife[0] = 'liu'
m3.value.age = 12;
m3.value.wife[0] = "liu";
console.log(m3.value.wife);
}
return {
m1,
m2,
m3,
handleUpdata,
};
},
});
</script>计算属性与监视
- 重点:计算属性的属性的类型是ref类型,属性.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
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<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>
姓名:<input type="text" placeholder="显示姓名" v-model="Fullname1" /><br />
姓名:<input type="text" placeholder="显示姓名" v-model="Fullname2" /><br />
姓名:<input type="text" placeholder="显示姓名" v-model="Fullname3" /><br />
</fieldset>
<button @click="handleUpdata">更新数据</button>
</template>
<script lang="ts">
import {
computed,
defineComponent,
reactive,
ref,
watch,
watchEffect,
} from "vue";
export default defineComponent({
name: "",
setup(props, { attrs, emit, slots }) {
let name = ref("he");
let user = reactive({
Firstname: "西门",
Lastname: "吹雪",
});
function handleUpdata() {
console.log(123);
}
//通过计算属性,实现第一个姓名显示
// vue3的计算属性,只传入一个回调函数,表示get,需要使用get和set,传入一个对象
// 返回的是一个ref的对象
// 第一个姓名
const Fullname1 = computed(() => {
return user.Firstname + user.Lastname;
});
// 第二个姓名
const Fullname2 = computed({
get() {
return user.Firstname + "_" + user.Lastname;
},
set(val: string) {
let name = val.split("_");
user.Firstname = name[0];
user.Lastname = name[1];
},
});
// 第三的姓名
const Fullname3 = ref("");
// 监视 ---监视指定的数据
// immediate:true 默认执行一次 deep:true 深度监视
watch(
user,
({ Firstname, Lastname }) => {
Fullname3.value = Firstname + "_" + Lastname;
},
{ immediate: true, deep: true }
);
// 不需要配置 immediate,就会执行一次
// watchEffect(()=>{
// Fullname3.value = user.Firstname + "_" + user.Lastname;
// })
watchEffect(() => {
const names = Fullname3.value.split("_");
user.Firstname = names[0];
user.Lastname = names[1];
});
// watch可以监视多个数据
// watch([user.Firstname,user.Lastname],()=>{
// // 这里代码没有执行,Fullname3是响应式的数据,user.Firstname和user.Lastname不是
// console.log('非响应式');
// })
// 如果watch监视的是响应式数据的属性时,使用回调的方式才可以监视他
watch([() => user.Firstname, () => user.Lastname, Fullname3], () => {
// 这里代码没有执行,Fullname3是响应式的数据,user.Firstname和user.Lastname不是
console.log("======");
});
return {
name,
user,
handleUpdata,
Fullname1,
Fullname2,
Fullname3,
};
},
});
</script>vue3和vue2生命周期对比
beforeCreate -> 使用 setup()
created -> 使用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured
app.vue
1 | <template> |
child.vue
1 | <template> |
. 自定义hook函数
- 作用:使用 Vue3 的组合 API 封装的可复用的功能函数
- 创建文件开头带use
- 自定义 hook 的作用类似于 vue2 中的 mixin 技术
- 自定义 Hook 的优势: 很清楚复用功能代码的来源, 更清楚易懂
App.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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71<template>
<h2>自定义hook函数</h2>
<h2>x:{{ x }},y:{{ y }}</h2>
<h3 v-if="loading">正在加载中</h3>
<h3 v-else-if="errorMsg">错误信息:{{ errorMsg }}</h3>
<ul v-else>
<li>id:{{ data.id }}</li>
<li>title:{{ data.address }}</li>
<li>price:{{ data.distance }}</li>
</ul>
<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>
</template>
<script lang='ts'>
import {
defineComponent,
onBeforeMount,
onMounted,
reactive,
ref,
watch,
} from "vue";
import usehook from "./hook/usehook";
import usereq from "./hook/usereq";
export default defineComponent({
name: "",
// 需求:用户在页面点击,把点击的横纵坐标收集展示
setup() {
// let x = ref("-1");
// let y = ref("-1");
// // 点击事件的回调函数
// const handleClick = (event: any) => {
// x.value = event.pageX;
// y.value = event.pageY;
// };
// // 页面加载完毕,进行点击操作
// onMounted(() => {
// window.addEventListener("click", handleClick);
// });
// onBeforeMount(() => {
// window.removeEventListener("click", handleClick);
// });
const { x, y } = usehook();
// 定义接口,约束对象的类型
interface AddressData {
id: number;
address: string;
distance: string;
}
interface Price {
id: number;
title: string;
price: number;
}
// 发送请求
// const { loading, data, errorMsg } = usereq<AddressData>("/data/address.json"); //获取对象数据
const { loading, data, errorMsg } = usereq<Price[]>("/data/data.json"); //获取对象数据
watch(data, () => {
if (data.value) {
console.log(data.value.length);
}
});
return { x, y, loading, data, errorMsg };
},
});
</script>hook/usereq.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// 引入axios
import axios from 'axios'
import { ref } from "vue";
// 发送ajax
export default function<T> (url: string) {
// 加载的状态
const loading = ref(true)
// 请求成功的数组
const data = ref<T|null>(null)
// 错误信息
const errorMsg = ref('')
axios.get(url).then(res => {
// 改变加载状态
loading.value = false
data.value = res.data
console.log(111111,data.value);
}).catch(err => {
console.log(err);
// 改变加载状态
loading.value = false
errorMsg.value = err.message || 'error'
})
return {
loading,
data,
errorMsg
}
}hook/usehook.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import { onBeforeMount, onMounted, ref } from "vue";
export default function () {
const x = ref(-1);
const y = ref(-1);
// 点击事件的回调函数
const handleClick = (event: MouseEvent) => {
x.value = event.pageX;
y.value = event.pageY;
};
// 页面加载完毕,进行点击操作
onMounted(() => {
window.addEventListener("click", handleClick);
});
onBeforeMount(() => {
window.removeEventListener("click", handleClick);
});
return {
x, y
}
}public/data/data.json
1
2
3
4
5
6
7
8
9
10[{
"id":2,
"title":"oppe",
"price":2999
},{
"id":78,
"title":"vivo",
"price":1599
}]public/data/address.json
1
2
3
4
5{
"id":1,
"address":"四川省成都市二仙桥",
"distance":"200m"
}toRefs的使用
- 问题: reactive 对象取出的所有属性值都是非响应式的*
- 解决: 利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性*
- 应用: 当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用*
const { name, age } = toRefs(state);
- toRefs 可以把reactive包裹的数据变成普通对象包裹的ref对象
- toRefs把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 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<template>
<h2>toRefs的使用</h2>
<!-- <h3>name:{{ state.name }}</h3>
<h3>age:{{ state.age }}</h3> -->
<h3>name:{{ name }}</h3>
<h3>age:{{ age }}</h3>
<hr />
<h3>name2:{{ name2 }}</h3>
<h3>age2:{{ age2 }}</h3>
</template>
<script lang="ts">
// 应用场景
function handleinfo() {
const state = reactive({
name2: "her",
age2: 233,
});
return {
...toRefs(state),
};
}
import { defineComponent, reactive, toRefs } from "vue";
export default defineComponent({
name: "",
setup() {
const state = reactive({
name: "he",
age: 23,
});
const { name2, age2 } = handleinfo();
// const state2 = toRefs(state)
const { name, age } = toRefs(state);
// toRefs 可以把reactive包裹的数据变成普通对象包裹的ref对象
// toRefs把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
// 定时器 更新数据 (如果数据变化了,页面也会跟着变化,证明是响应式的数据)
setInterval(() => {
// state.name += "===";
// state2.name.value += "===";
// name.value += "===";
}, 2000);
return {
state,
// ...state, // 不是响应式的数据
// ...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: "",
// 功能需求: 让输入框自动获取焦点
setup(props, { attrs, emit, slots }) {
// 默认是空的,页面加载完毕,说明组件已经存在,获取文本框元素
const inputRef = ref<HTMLElement|null>(null);
onMounted(()=>{
inputRef.value && inputRef.value.focus() //自动获取焦点
})
return {
inputRef,
};
},
});
</script>