计算属性&侦听器
# 计算属性&侦听器
# computed 🔥
- 在Composition API中,我们可以在 setup 函数中使用 computed 方法来编写一个计算属性
- 接收一个getter函数,并为 getter 函数返回的值,返回一个不变的 ref 对象
- 接收一个具有 get 和 set 的对象,返回一个可变的(可读写)ref 对象
<template>
<div>
<h2>{{ fullName }}</h2>
<button @click="changeName">修改firstName</button>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
setup() {
const firstName = ref('Kobe')
const lastName = ref('Bryant')
// 1.用法一: 传入一个getter函数
// computed的返回值是一个ref对象
// const fullName = computed(() => firstName.value + ' ' + lastName.value)
// const fullName2 = computed(() => firstName.value + ' @ ' + lastName.value)
// 2.用法二: 传入一个对象, 对象包含getter/setter
const fullName = computed({
get: () => firstName.value + ' ' + lastName.value,
set(newValue) {
const names = newValue.split(' ')
firstName.value = names[0]
lastName.value = names[1]
},
})
const changeName = () => {
// firstName.value = 'James'
fullName.value = 'coder why'
}
return {
fullName,
changeName,
}
},
}
</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
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
# watchEffect—立即&自动收集响应式数据的依赖 🔥
# 介绍 🔥
侦听data或者props的数据变化
- watchEffect传入的函数会被立即执行一次,并且在执行的过程中会收集依赖
- 只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行
<template>
<div>
<h2>{{name}}-{{age}}</h2>
<button @click="changeName">修改name</button>
<button @click="changeAge">修改age</button>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
// watchEffect: 自动收集响应式的依赖
const name = ref("why");
const age = ref(18);
const changeName = () => name.value = "kobe"
const changeAge = () => age.value++
watchEffect(() => {
console.log("name:", name.value, "age:", age.value);
});
return {
name,
age,
changeName,
changeAge
}
}
}
</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
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
# 停止侦听 🔥
如果在发生某些情况下,我们希望停止侦听,这个时候我们可以获取watchEffect的返回值函数,调用该函数即可
<template>
<div>
<h2>{{name}}-{{age}}</h2>
<button @click="changeName">修改name</button>
<button @click="changeAge">修改age</button>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
// watchEffect: 自动收集响应式的依赖
const name = ref("why");
const age = ref(18);
const stop = watchEffect(() => {
console.log("name:", name.value, "age:", age.value);
});
const changeName = () => name.value = "kobe"
const changeAge = () => {
age.value++;
if (age.value > 25) {
stop();
}
}
return {
name,
age,
changeName,
changeAge
}
}
}
</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
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
# 清除副作用—onInvalidate 🔥
什么是清除副作用呢?
- 比如在开发中我们需要在侦听函数中执行网络请求,但是在网络请求还没有达到的时候,我们停止了侦听器,或者侦听器侦听函数被再次执行了。
- 那么上一次的网络请求应该被取消掉,这个时候我们就可以清除上一次的副作用;
在我们给watchEffect传入的函数被回调时,其实可以获取到一个参数onInvalidate,这个失效回调会被触发:
- 副作用即将重新执行时(每次依赖的数据变化时)
- 侦听器被停止 (如果在
setup()或生命周期钩子函数中使用了watchEffect,则在组件卸载时)
<template>
<div>
<h2>{{name}}-{{age}}</h2>
<button @click="changeName">修改name</button>
<button @click="changeAge">修改age</button>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
setup() {
// watchEffect: 自动收集响应式的依赖
const name = ref("why");
const age = ref(18);
const stop = watchEffect((onInvalidate) => {
const timer = setTimeout(() => {
console.log("网络请求成功~");
}, 2000)
// 根据name和age两个变量发送网络请求
onInvalidate(() => {
// 在这个函数中清除额外的副作用
// request.cancel()
clearTimeout(timer);
console.log("onInvalidate");
})
console.log("name:", name.value, "age:", age.value);
});
const changeName = () => name.value = "kobe"
const changeAge = () => {
age.value++;
if (age.value > 25) {
stop();
}
}
return {
name,
age,
changeName,
changeAge
}
}
}
</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
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
# 执行时机—flush 🔥
如果需要在组件更新(例如:当与模板引用 (opens new window)一起)后重新运行侦听器副作用,我们可以传递带有 flush 选项的附加 options 对象 (默认为 'pre'):
flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
1
pre:在组件更新后触发,这样你就可以访问更新的 DOM。这也将推迟副作用的初始运行,直到组件的首次渲染完成
post:默认
sync:强制效果始终同步触发。然而,这是低效的,应该很少需要。
3.2+,推荐使用 watchPostEffect、watchSyncEffect
<template>
<div>
<h2 ref="title">哈哈哈</h2>
</div>
</template>
<script>
import { ref, watchEffect } from 'vue'
export default {
setup() {
// 单个 ref
const title = ref(null)
watchEffect(
() => {
console.log(title.value)
},
{
// 当没有该配置,或者为默认值 pre,则上面log打印2次,第一次为null,第二次为el
// 这是因为setup函数在执行时就会立即执行传入的副作用函数,这个时候DOM并没有挂载,所以打印为null
// 而当DOM挂载时,会给title的ref对象赋值新的值,副作用函数会再次执行,打印出来对应的元素
flush: 'post',
}
)
return {
title,
}
},
}
</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
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
# watchPostEffect
3.2+。watchEffect 的别名,带有 flush: 'post' 选项。
# watchSyncEffect
3.2+。watchEffect 的别名,带有 flush: 'sync' 选项。
# watch—惰性&手动指定侦听的数据源
# 介绍 🔥
侦听data或者props的数据变化
watch的API完全等同于组件watch选项的Property:
- watch需要侦听特定的数据源,并在回调函数中执行副作用;
- 默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调;
与watchEffect的比较,watch允许我们:
- 懒执行副作用(第一次不会直接执行);
- 更具体的说明当哪些状态发生变化时,触发侦听器的执行;
- 访问侦听状态变化前后的值;
# 侦听单个数据源 🔥
watch侦听函数的数据源有两种类型:
- 一个getter函数,但是该getter函数必须引用可响应式的对象(比如reactive或者ref);
- 直接写入一个可响应式的对象,reactive或者ref(比较常用的是ref,reactive类型需要进行某些转换);
<template>
<div>
<h2 ref="title">{{ info.name }}</h2>
<button @click="changeData">修改数据</button>
</div>
</template>
<script>
import { ref, reactive, watch } from 'vue'
export default {
setup() {
const info = reactive({ name: 'why', age: 18 })
// 1.侦听watch时,传入一个getter函数
watch(
() => info.name,
(newValue, oldValue) => {
console.log('newValue:', newValue, 'oldValue:', oldValue)
}
)
// 2.传入一个可响应式对象: reactive对象
// 2.1 情况一: reactive对象获取到的newValue和oldValue本身都是reactive对象。所以new、old值为reactive,且相同!
// watch(info, (newValue, oldValue) => {
// console.log("newValue:", newValue, "oldValue:", oldValue);
// })
// 2.2 情况二:如果希望newValue和oldValue是一个普通的对象,需解构!!!
watch(
() => {
return { ...info }
},
(newValue, oldValue) => {
console.log('newValue:', newValue, 'oldValue:', oldValue)
}
)
// 3.传入一个可响应式对象: ref对象
// ref对象获取newValue和oldValue是value值的本身,所以new、old不同
// const name = ref("why");
// watch(name, (newValue, oldValue) => {
// console.log("newValue:", newValue, "oldValue:", oldValue);
// })
const changeData = () => {
info.name = 'kobe'
}
return {
changeData,
info,
}
},
}
</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
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>
<div>
<h2 ref="title">{{info.name}}</h2>
<button @click="changeData">修改数据</button>
</div>
</template>
<script>
import { ref, reactive, watch } from 'vue';
export default {
setup() {
// 1.定义可响应式的对象
const info = reactive({name: "why", age: 18});
const name = ref("why");
// 2.侦听器watch
watch([() => ({...info}), name], ([newInfo, newName], [oldInfo, oldName]) => {
console.log(newInfo, newName, oldInfo, oldName);
})
const changeData = () => {
info.name = "kobe";
}
return {
changeData,
info
}
}
}
</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
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
# 源码—查看侦听类型 🔥
/packages/runtime-core/src/apiWatch.ts#doWatch
const instance = currentInstance
let getter: () => any
let forceTrigger = false
let isMultiSource = false
if (isRef(source)) {
getter = () => source.value
forceTrigger = !!source._shallow
} else if (isReactive(source)) {
getter = () => source
deep = true
} else if (isArray(source)) {
isMultiSource = true
forceTrigger = source.some(isReactive)
getter = () =>
source.map(s => {
if (isRef(s)) {
return s.value
} else if (isReactive(s)) {
return traverse(s)
} else if (isFunction(s)) {
return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
} else {
__DEV__ && warnInvalidSource(s)
}
})
} else if (isFunction(source)) {
if (cb) {
// getter with cb
getter = () =>
callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
} else {
// no cb -> simple effect
getter = () => {
if (instance && instance.isUnmounted) {
return
}
if (cleanup) {
cleanup()
}
return callWithAsyncErrorHandling(
source,
instance,
ErrorCodes.WATCH_CALLBACK,
[onInvalidate]
)
}
}
} else {
getter = NOOP
__DEV__ && warnInvalidSource(source)
}
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
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
可以看出,总共支持监听4中类型:
- ref:getter 取的是ref的value,是普通数据类型,所以new、old值不同!
- reactive:getter取的是原reactive的Proxy对象,所以new、old值同为最新的reactive的Proxy对象!
- function(即getter)
- array:即监听多个数据源,处理方式还是按照map后单个处理!!!
# 深层的侦听—deep 🔥
- 查看上面源码,注意 reactive 对象默认 deep 为 true!!!但是该方式 getter 直接取 reactive 对象,若需要返回普通对象,则需要侦听器传入解构对象。且若需要深度侦听,则需手动指定 deep 为 true
- 其他类型根据需要设置 deep 的值
# 立即执行—immediate 🔥
默认情况下,watch第一次不会执行,必须侦听的值改变了!
编辑 (opens new window)
上次更新: 2022/03/23, 17:55:39