Vue 3 学习笔记

Composition API(常用部分)

setup

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

ref

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

reactive

  • 作用: 定义多个数据的响应式
  • const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
  • 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
1
2
3
4
5
6
7
8
9
10
11
12
13
const obj = {
name: "xiao",
age: 18,
wife: {
name: "xiaohong",
age: 20,
cars: ["1", "2", "3"],
},
};
// 返回的是一个Proxy的代理对象,被代理的目标就是obj对象
// user现在是代理对象,obj是目标对象
// user对象的类型是proxy
const user = reactive<any>(obj);

注:直接使用目标对象的方式来更新目标对象中的成员的值,是不行的,只能使用代理对象的方式来更新数据(响应式数据)

1
2
3
4
5
6
7
8
9
10
11
const updateUser = () => {
// 直接使用目标对象的方式来更新目标对象中的成员的值,是不行的,只能使用代理对象的方式来更新数据(响应式数据)
// obj.name+="866hhhh" // 界面没有更新
user.name += "866hhhh"; // 界面更新

// obj.gender="男" // 界面没有更新
user.gender="男" // 界面更新

// delete obj.age; // 界面没有更新
delete user.age; // 界面更新
};

比较Vue2与Vue3的响应式

vue2的响应式

  • 核心:
    • 对象: 通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视/拦截)
    • 数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
1
2
3
4
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
  • 问题
    • 对象直接新添加的属性或删除已有属性, 界面不会自动更新
    • 直接通过下标替换元素或更新length, 界面不会自动更新 arr[1] = {}

Vue3的响应式

  • 核心:
    • 通过 Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等…
    • 通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作
    • 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
<script type="text/javascript">
// 目标对象
const user = {
name: '为冒',
age: 23,
wife: {
name: "耐俭预",
age: 19,
}
}
// 把目标编程代理对象
// 参数1:user--->target目标对象
// 参数2:handler--->处理器对象,用来监视数据,及数据的操作
const proxyUser = new Proxy(user, {
get(target, property) {
console.log("get方法");
return Reflect.get(target, property)
},
// 不仅仅修改目标对象的属性值/为目标对象添加新的属性
set(target, property,val) {
console.log("set方法");
return Reflect.set(target, property,val)
},
deleteProperty(target, property){
console.log("deleteProperty方法");
return Reflect.deleteProperty(target, property)
}
})

console.log(proxyUser.name);
proxyUser.name = "hhhhh"
console.log(user);
delete proxyUser.name
console.log(user);
proxyUser.wife.name="当之贼"
console.log(user);
</script>

setup细节

  • setup执行的时机
    • 在beforeCreate之前执行(一次), 此时组件对象还没有创建
    • this是undefined, 不能通过this来访问data/computed/methods / props
    • 其实所有的composition API相关回调函数中也都不可以
  • setup的返回值
    • 一般都返回一个对象: 为模板提供数据, 也就是模板中可以直接使用此对象中的所有属性/方法
    • 返回对象中的属性会与data函数返回对象的属性合并成为组件对象的属性
    • 返回对象中的方法会与methods中的方法合并成功组件对象的方法
    • 如果有重名, setup优先
    • 注意:
      • 一般不要混合使用: methods中可以访问setup提供的属性和方法, 但在setup方法中不能访问data和methods
      • setup不能是一个async函数: 因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性数据
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
<template>
<h2>App</h2>
<p>msg: {{msg}}</p>
<button @click="fn('--')">更新</button>

<child :msg="msg" msg2="cba" @fn="fn"/>
</template>

<script lang="ts">
import {
reactive,
ref,
} from 'vue'
import child from './child.vue'

export default {

components: {
child
},

setup () {
const msg = ref('abc')

function fn (content: string) {
msg.value += content
}
return {
msg,
fn
}
}
}
</script>
  • setup的参数
    • setup(props, context) / setup(props, {attrs, slots, emit})
    • props: 包含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
62
63
64
65
66
67
68
69
70
71
72
<template>
<div>
<h3>{{n}}</h3>
<h3>{{m}}</h3>
<h3>msg: {{msg}}</h3>
<h3>msg2: {{$attrs.msg2}}</h3>
<slot name="xxx"></slot>
<button @click="update">更新</button>
</div>
</template>

<script lang="ts">

import {
ref,
defineComponent
} from 'vue'

export default defineComponent({
name: 'child',

props: ['msg'],

emits: ['fn'], // 可选的, 声明了更利于程序员阅读, 且可以对分发的事件数据进行校验

data () {
console.log('data', this)
return {
// n: 1
}
},

beforeCreate () {
console.log('beforeCreate', this)
},

methods: {
// update () {
// this.n++
// this.m++
// }
},

// setup (props, context) {
setup (props, {attrs, emit, slots}) {

console.log('setup', this)
console.log(props.msg, attrs.msg2, slots, emit)

const m = ref(2)
const n = ref(3)

function update () {
// console.log('--', this)
// this.n += 2
// this.m += 2

m.value += 2
n.value += 2

// 分发自定义事件
emit('fn', '++')
}

return {
m,
n,
update,
}
},
})
</script>

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
<template>
<div>
<h2>reactive和ref细节问题</h2>
<h3>m1:{{ m1 }}</h3>
<h3>m2:{{ m2 }}</h3>
<h3>m3:{{ m3 }}</h3>
<hr />
<button @click="update">update</button>
</div>
</template>

<script lang="ts">
import { defineComponent, reactive, ref } from "vue";

export default defineComponent({
setup() {
// ref方式设置
const m1 = ref("abc");
console.log(m1);
// reactive 方式
const m2 = reactive({ name: "m2", wife: { name: "m2-wife" } });
// ref 设置对象
const m3 = ref({ name: "m3", wife: { name: "m3-wife" } });
console.log(m3);
const update = () => {
m1.value += "=====";
m2.wife.name += "----";
m3.value.wife.name += "?????";
};

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

计算属性与监视

  • 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
<template>
<div>
<h2>计算属性与监视</h2>
<fieldset>
<legend>姓名操作</legend>
姓氏:<input type="text" placeholder="请输入姓氏" v-model="user.firstName"/><br />
名字:<input type="text" placeholder="请输入名字" v-model="user.lastName"/>
</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" />
</fieldset>
</div>
</template>

<script lang="ts">
import {
computed,
defineComponent,
reactive,
ref,
watch,
watchEffect,
} from "vue";

export default defineComponent({
setup() {
const user = reactive({
firstName: "东方",
lastName: "不败",
});
// 计算属性的函数只传入一个回调函数,表示的是get
// 返回的是一个ref类型的对象
const fullName1 = computed(() => {
return user.firstName + "_" + user.lastName;
});
const fullName2 = computed({
get() {
return user.firstName + "_" + user.lastName;
},
set(val: string) {
const name = val.split("_");
user.firstName = name[0];
user.lastName = name[1];
},
});
const fullName3 = ref("");
// immediate 默认执行一次watch deep深度监听
watch(user,({ firstName, lastName }) => {
fullName3.value = firstName + "_" + lastName;
},
{ immediate: true, deep: true }
);

// 监视,不需要配置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是响应式的数据,但是,user.firstName,user.lastName不是响应式的数据
// console.log("=======");
// });
// 当我们使用watch监听非响应式的数据的时候,代码需要改一下
watch([()=>user.firstName, ()=>user.lastName],() => {
console.log("=======");
});
return { user, fullName1, fullName2, fullName3 };
},
});
</script>

<style scoped>
</style>

生命周期

vue2.x的生命周期

lifecycle_vue2

vue3.x的生命周期

  • vue2.x的beforeDestroydestroyed在vue3中更名为beforeUnmountunmounted
  • beforeDestroy=>beforeUnmount
  • destroyed=>unmounted
image-20210505163008767

与 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
79
<!--
* @Author: 橘子味雪糕
* @Date: 2021-05-04 17:25:01
* @LastEditTime: 2021-05-05 17:15:05
* @FilePath: \blogd:\WorkingArea\VScodeWorkspace\vue3-learn\vue3_study_cli\src\components\child.vue
-->
<template>
<div>
<h2>子级组件</h2>
<h4>msg:{{ msg }}</h4>
<button @click="update">update</button>
</div>
</template>

<script lang="ts">
import { defineComponent, onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, onUpdated, ref } from "vue";

export default defineComponent({
// 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中改名了
// beforeDestroy=>beforeUnmount
// destroyed=>unmounted
beforeUnmount() {
console.log("2.x===>beforeDestroy");
},
unmounted() {
console.log("2.x===>destroyed");
},
setup() {
console.log("%c 3.0===>setup",'color:blue;');
const msg = ref("abc");
const update = () => {
msg.value += "hhhhh";
};

onBeforeMount(()=>{
console.log("%c 3.0===>onBeforeMount",'color:blue;');
})
onMounted(()=>{
console.log("%c 3.0===>onMounted",'color:blue;');
})
onBeforeUpdate(()=>{
console.log("%c 3.0===>onBeforeUpdate",'color:blue;');
})
onUpdated(()=>{
console.log("%c 3.0===>onUpdated",'color:blue;');
})
onBeforeUnmount(()=>{
console.log("%c 3.0===>onBeforeUnmount",'color:blue;');
})
onUnmounted(()=>{
console.log("%c 3.0===>onUnmounted",'color:blue;');
})
return { msg, update };
},
});
</script>

<style scoped>

</style>

自定义hook函数

  • 使用Vue3的组合API封装的可复用的功能函数
  • 自定义hook的作用类似于vue2中的mixin技术
  • 自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { onBeforeUnmount, onMounted, ref } from "vue"

export default function(){
const x=ref(-1)
const y=ref(-1)
onMounted(()=>{
window.addEventListener('click',clickHandler)
})
const clickHandler=(event:MouseEvent)=>{
x.value=event.pageX
y.value=event.pageY
console.log(event);
}
// 页面卸载之前
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
<template>
<div>
<h2>自定义hook函数</h2>
<h2>x:{{ x }}</h2>
<h2>y:{{ y }}</h2>
<h3 v-if="loading">加载中...</h3>
<h3 v-else-if="errorMsg">错误信息:{{ errorMsg }}</h3>
<h3 v-else>数据:{{ data }}</h3>
</div>
</template>

<script lang="ts">
import { defineComponent, watch } from "vue";
import useMousePosition from "./hooks/useMousePosition";
import useRequest from "./hooks/useRequest";

// 定义接口约束对象类型
interface AddressData {
id: number;
name: string;
age: number;
}

export default defineComponent({
// 需求1:用户在页面中点击页面,把页面的位置坐标收集起来
setup() {
const { x, y } = useMousePosition();
// 需求2: 封装发ajax请求的hook函数
// 利用TS泛型强化类型检查
const { loading, errorMsg, data } = useRequest<AddressData[]>(
"http://localhost:3000/data/list.json"
);

watch(data, () => {
console.log(data.value.length);
});

return { x, y, loading, errorMsg, data };
},
});
</script>

<style scoped>
</style>

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
<template>
<div>
<h2>toRefs的使用</h2>
<!-- <h3>name:{{user.name}}</h3>
<h3>age:{{user.age}}</h3> -->
<h3>name:{{name}}</h3>
<h3>age:{{age}}</h3>
</div>
</template>

<script lang="ts">
import { defineComponent, reactive, toRef, toRefs } from "vue";

export default defineComponent({
setup() {
const user = reactive({
name: "阿斯达",
age: 18,
});

// const user2=toRefs(user)
const {name,age}=toRefs(user)
// toRefs可以把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
// 定时器更新数据
setInterval(()=>{
// user.name+='====='
name.value+='====='
},1000)

return {
// user,
// ...user // 不是响应式的数据了
// ...user2
name,
age,
// ...toRefs(user)
};
},
});
</script>

<style scoped>
</style>

ref获取元素

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<div>
<h2>ref的另一个作用:获取页面元素</h2>
<input type="text" ref="inputRef">
</div>
</template>

<script lang="ts">
import { defineComponent,onMounted,ref } from "vue";

export default defineComponent({
// 当页面加载完毕后,页面中的文本框自动获取焦点
setup() {
// 默认空,页面加载完毕,说明组件已经存在了
const inputRef=ref<HTMLElement|null>(null)
// 页面加载后的生命周期组合API
onMounted(()=>{
inputRef.value&&inputRef.value.focus()
})
return {inputRef};
},
});
</script>

<style scoped>
</style>

Composition API(其它部分)

shallowReactive 与 shallowRef

  • shallowReactive : 只处理了对象内最外层属性的响应式(也就是浅响应式)
  • shallowRef: 只处理了value的响应式, 不进行对象的reactive处理
  • 什么时候用浅响应式呢?
    • 一般情况下使用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
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
<template>
<div>
<h2>shallowReactive 与 shallowRef</h2>
<h3>m1:{{ m1 }}</h3>
<h3>m2:{{ m2 }}</h3>
<h3>m3:{{ m3 }}</h3>
<h3>m4:{{ m4 }}</h3>
<hr>
<button @click="update">update</button>
</div>
</template>

<script lang="ts">
import {
defineComponent,
reactive,
ref,
shallowReactive,
shallowRef,
} from "vue";

export default defineComponent({
setup() {
// 深度响应式
const m1 = reactive({
name: "vue",
age: 1,
cart: {
name: "rafaBenitez",
color: "red",
},
});
// 浅度响应式
const m2 = shallowReactive({
name: "vue",
age: 1,
cart: {
name: "rafaBenitez",
color: "red",
},
});
// 深度响应式
const m3 = ref({
name: "vue",
age: 1,
cart: {
name: "rafaBenitez",
color: "red",
},
});
// 浅度响应式
const m4 = shallowRef({
name: "vue",
age: 1,
cart: {
name: "rafaBenitez",
color: "red",
},
});
const update=()=>{
// 更改m1==>reactive
// m1.cart.name+='====' // 改变

// 更改m2==>shallowReactive
// m2.name+='===' // 改变
// m2.cart.name+='===' // 不改变

// 更改m3==>ref
// m3.value.cart.name+='===>' // 改变

// 更改m4==>shallowRef
// m4.value.name+='====' // 不改变
// m4.value.cart.name+='====' // 不改变
}
return {
m1,
m2,
m3,
m4,
update
};
},
});
</script>

<style scoped>
</style>

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
37
38
<template>
<div>
<h2>readonly 与 shallowReadonly</h2>
<h3>user:{{ user }}</h3>
<h3>user1:{{ user1 }}</h3>
<hr />
<button @click="update">update</button>
</div>
</template>

<script lang="ts">
import { defineComponent, reactive, readonly, shallowReadonly } from "vue";

export default defineComponent({
setup() {
const user = reactive({
name: "米娅",
age: 20,
car: {
name: "艺画开天",
age: 3,
},
});
// readonly 深度只读数据
const user1=readonly(user)
// shallowReadonly 浅层只读
// const user1 = shallowReadonly(user);
const update = () => {
// user1.name+='===='
user1.car.name += "====";
};
return { user,user1, update };
},
});
</script>

<style scoped>
</style>

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
<template>
<div>
<h2>toRaw 与 markRaw</h2>
<h3>user:{{ user }}</h3>
<hr />
<button @click="testToRaw">test-toRaw</button>
<button @click="testMarkRaw">test-markRaw</button>
</div>
</template>

<script lang="ts">
import { defineComponent, markRaw, reactive, toRaw } from "vue";

interface UserInfo {
name: string;
age: number;
likes?: string[];
}

export default defineComponent({
setup() {
const user = reactive<UserInfo>({ name: "小", age: 10 });
const testToRaw = () => {
// 把代理对象变成普通对象,数据变化,界面不变化
const user1 = toRaw(user);
user1.name += "===";
console.log("hhhhh");
};
const testMarkRaw = () => {
// user.likes = ["吃", "喝"];
// user.likes[0] += "====";
const likes = ["1", "2"];
// markRaw标记的对象数据,从此以后都不能成为代理对象了
user.likes = markRaw(likes);
setInterval(() => {
user.likes[0] += "====";
console.log("kkkk");
}, 1000);
};
return { user, testToRaw, testMarkRaw };
},
});
</script>

<style scoped>
</style>

toRef

  • 为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
  • 区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
  • 应用: 当要将 某个prop 的 ref 传递给复合函数时,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
49
<template>
<div>
<h2>toRef的使用</h2>
<h3>state:{{ state }}</h3>
<h3>age:{{ age }}</h3>
<h3>money:{{ money }}</h3>
<hr />
<button @click="update">update</button>
<Child :age="age"/>
</div>
</template>

<script lang="ts">
import { defineComponent, reactive, toRef, ref } from "vue";
import Child from "./components/child.vue";
export default defineComponent({
components: {
Child
},
setup() {
const state = reactive({
age: 5,
money: 100,
});
// 把响应式数据state对象中的某个属性变成了ref对象了
const age = toRef(state, "age");
// 把响应式对象中的某个属性使用ref进行包装,变成了一个ref对象
const money = ref(state.money);
console.log(age);
console.log(money);
const update = () => {
state.age+=2
// age.value+=2
// state.money+=20
// money.value += 20;
console.log("sss");
};
return {
state,
age,
money,
update,
};
},
});
</script>

<style scoped>
</style>
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
<!--
* @Author: 橘子味雪糕
* @Date: 2021-05-07 14:07:38
* @LastEditTime: 2021-05-07 14:09:54
* @FilePath: \blogd:\WorkingArea\VScodeWorkspace\vue3-learn\vue3_study_vite2\src\components\child.vue
-->
<template>
<h2>Child</h2>
<h3>{{ age }}</h3>
<h3>{{ length }}</h3>
</template>

<script lang="ts">
import { computed, defineComponent, Ref, toRef } from "vue";

const component = defineComponent({
props: {
age: {
type: Number,
require: true,
},
},

setup(props, context) {
const length = useFeatureX(toRef(props, "age"));

return {
length,
};
},
});

function useFeatureX(age: Ref) {
const lenth = computed(() => age.value.toString().length);

return lenth;
}

export default component;
</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
46
47
48
49
50
51
<template>
<div>
<h2>customRef的使用</h2>
<input type="text" v-model="keyword" />
<p>{{ keyword }}</p>
</div>
</template>

<script lang="ts">
import { customRef, defineComponent, ref } from "vue";

// value 传入的数据,将来数据的类型不确定,所以,用泛型,delay防抖的间隔时间
function useDebouncedRef<T>(value: T, delay = 200) {
// 准备一个存储定时器的id变量
let timeOutId: number;
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({
setup() {
// const keyword = ref("abc");
const keyword = useDebouncedRef("abc", 500);
return {
keyword,
};
},
});
</script>

<style scoped>
</style>

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
<template>
<h1>父组件</h1>
<p>当前颜色: {{color}}</p>
<button @click="color='red'">红</button>
<button @click="color='yellow'">黄</button>
<button @click="color='blue'">蓝</button>

<hr>
<Son />
</template>

<script lang="ts">
import { provide, ref } from 'vue'
/*
- provide` 和 `inject` 提供依赖注入,功能类似 2.x 的 `provide/inject
- 实现跨层级组件(祖孙)间通信
*/

import Son from './Son.vue'
export default {
name: 'ProvideInject',
components: {
Son
},
setup() {

const color = ref('red')

provide('color', color)

return {
color
}
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>
<h2>子组件</h2>
<hr>
<GrandSon />
</div>
</template>

<script lang="ts">
import GrandSon from './GrandSon.vue'
export default {
components: {
GrandSon
},
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<h3 :style="{color}">孙子组件: {{color}}</h3>

</template>

<script lang="ts">
import { inject } from 'vue'
export default {
setup() {
const color = inject('color')

return {
color
}
}
}
</script>

响应式数据的判断

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

Composition API VS Option API

Option API的问题

  • 在传统的Vue OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 ,滚动条反复上下移动

imgimg

使用Compisition API

  • 我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起
img img img