Vue3响应式API

reactive

  • 接收一个对象,返回一个响应式的被Proxy包装过的对象(返回的对象 不等于 源对象)
    • 基本类型无法被 reactive 代理
  • 等同于 Vue2.0 中的 Vue.observable()
  • 原理:Proxy 代理
    • 深度响应
    • 递归代理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div>{{ proxyObj.name }}</div>
</template>

<script>
import { reactive } from 'vue';

export default {
name: 'App',
setup() {
const proxyObj = reactive({
name: 'Lance',
age: 233
});

console.log(proxyObj);

return {
proxyObj
}
}
}
</script>
1
2
3
4
5
6
7
8
9
10
<template></template>
<script>
import { reactive } from 'vue';
export default {
name: 'App',
setup() {
const obj = reactive(1); // 无法包装基本类型
}
}
</script>

🌈 ref

  • 数据响应式
    • ref(基本类型的值)
      • return 一个 RefObject,其中的 value 就是ref包裹的值
    • ref(复杂类型的值)
      • return 一个 RefObject,其中的 value 是用 reactive 包装过的 Proxy 代理对象
  • 得通过 **.value** 的方式读写属性
  • return 出去后会被 vue 自动拆包,template 中不需要 .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
<template>
<div>{{ count }}</div>
</template>

<script>
import { ref } from 'vue';

export default {
name: 'App',
setup() {
const count = ref(0);
console.log("count:", count);
console.log("===");
console.log("count.value:", count.value);
console.log("===");

const obj = ref({
name: 'Lance',
age: 233,
info: {
grade: 120
}
});

console.log("obj", obj);
console.log("===");
console.log("obj.value", obj.value); // 第一层需要 .value 拆包
console.log("===");
console.log("obj.value.info", obj.value.info); // 后续都是 Proxy 对象了,不用拆包
console.log("===");
console.log("obj.value.info.grade", obj.value.info.grade);

return {
count,
obj
}
}
}
</script>

reactive包裹ref

  • 就不用再对此 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
<template>
<div>{{ proxyObj.count }}</div>
</template>

<script>
import { ref, reactive } from 'vue';

export default {
name: 'App',
setup() {
const count = ref(0);
const proxyObj = reactive({
name: 'Lance',
age: 233,
count // 相当于vue给你解包了:count: count.value
});

console.log(proxyObj.count); // 所以此处不用再 .value,template 中 也不用

return {
proxyObj
}
}
}
</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
23
24
25
26
27
28
<template>
<div>{{ count }}</div>
</template>

<script>
import { reactive, ref } from 'vue';

export default {
name: 'App',
setup() {
const count = ref(0);

const state = reactive({
count
});

const newCount = ref(1);
state.count = newCount; // 新的ref替换旧的ref
console.log("state.count:", state.count); // 新的会覆盖旧的
console.log("count:", count.value); // 原来的不变

return {
count,
state
}
}
}
</script>

ref放进数组、Map等原始集合类型的reactive中时不会自动拆包

  • native collection type(array、Map、Set、…)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div>{{ count }}</div>
</template>

<script>
import { reactive, ref } from 'vue';

export default {
name: 'App',
setup() {
const count = ref(0);

const arr = reactive([count]);
console.log(arr[0].value); // 需要手动拆包

const map = reactive( new Map( [['name', ref(233)]] ) );
console.log(map.get('name').value); // 需要手动拆包

return {
count
}
}
}
</script>

unref

  • 拆包语法糖
  • 如果被包裹值是个 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
<template></template>

<script>
import { isRef, ref } from 'vue';
export default {
name: 'App',
setup() {
const info = {
name: 'Lance',
age: 233
};
const refInfo = ref({
name: 'GC',
age: 111
});

const obj = isRef(info) ? info.value : info;
const obj2 = isRef(refInfo) ? refInfo.value : refInfo;
console.log(obj);
console.log(obj2);
}
}
</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
<template>
<div></div>
</template>

<script>
import { ref, unref } from 'vue';
export default {
name: 'App',
setup() {
const info = {
name: 'Lance',
age: 2333
};
const refInfo = ref({
name: 'GC',
age: 111
});

const obj = unref(info); // 返回原始对象
const obj2 = unref(refInfo); // 会拆包,返回 Proxy 对象
console.log(obj);
console.log(obj2);
}
}
</script>

toRef

  • 主要针对的是 reactive 中的数据

  • toRef 的值更新,会同步 reactive 对象中的相同属性更新

  • reactive 对象属性更新,对应 toRef 值也会更新

  • 使用场景:

      • 不需要整个 reactive 响应式对象,只想使用其下的某个属性单独拿出来作为响应式 ref 使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template></template>

<script>
import { reactive, toRef } from 'vue';

export default {
name: 'App',
setup() {
const state = reactive({
name: 'Lance'
});
const nameRef = toRef(state, 'name');
nameRef.value = 'GC';
console.log(state.name); // 同步更新了
state.name = 'Lance';
console.log(nameRef.value); // 也更新了
}
}
</script>

使用场景:自定义 composition api 中使用响应式对象下某个属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template></template>

<script>
import { reactive, toRef } from 'vue';

function useDoSth(name) {
return `My name is ${name.value}`;
}
export default {
name: 'App',
setup() {
const state = reactive({
name: 'Lance'
});
const nameRef = toRef(state, 'name');

const sentence = useDoSth(nameRef);
console.log(sentence);
}
}
</script>

🌈 toRefs

  • 给 reactive 响应式对象下每个属性都转成 ref
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template></template>

<script>
import { reactive, toRefs } from 'vue';

export default {
name: 'App',
setup() {
const state = reactive({
name: 'Lance',
age: 233,
info: {
grade: 100
}
});
const stateRefs = toRefs(state);
console.log(stateRefs);
console.log(stateRefs.name.value);
}
}
</script>

作用

  • 展开 reactive ,能在 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
<template>
<div>
<!-- 通过 reactive 访问属性 -->
<div>{{ state.name }}</div>
<!-- 通过 toRefs reactive 之后越过对象名直接访问属性 -->
<div>{{ name }}</div>
</div>
</template>

<script>
import { reactive, toRefs } from 'vue';

export default {
name: 'App',
setup() {
const state = reactive({
name: 'Lance',
age: 233,
info: {
grade: 100
}
});
const stateRefs = toRefs(state);

return {
state,
...stateRefs
}
}
}
</script>

isRef

  • 判断一个值是不是 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
<template></template>

<script>
import { reactive, toRefs, isRef } from 'vue';

export default {
name: 'App',
setup() {
const state = reactive({
name: 'Lance',
age: 233,
info: {
grade: 100
}
});
const stateRefs = toRefs(state);
console.log("isRef(stateRefs):", isRef(stateRefs));
console.log("isRef(stateRefs.name):", isRef(stateRefs.name));
return {
state,
...stateRefs
}
}
}
</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
42
43
44
45
46
47
48
49
<template>
<div>
<span>{{ text }}</span>
<input type="text" v-model="text">
</div>
</template>

<script>
import { customRef } from 'vue';

/**
* value: 值
* delay: 延迟
*/
function useDebounce(value, delay = 200) {
let t = null;

// 返回 customRef, customRef 接收一个工厂函数
/**
* track: getter访问时执行
* trigger: setter触发更新时执行
*/
return customRef((track, trigger) => {
// 返回一个对象, 包含 getter、setter
return {
get() {
track();
return value;
},
set(newVal) {
clearTimeout(t);
t = setTimeout(() => {
value = newVal;
trigger();
}, delay);
}
}
});
}
export default {
name: 'App',
setup() {
const text = useDebounce('', 500);
return {
text
}
}
}
</script>

shallowRef/triggerRef

shallowRef

  • 如果 shallowRef() 的是个对象,则不会把它包装成 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
44
45
46
47
48
49
50
51
52
53
54
55
<template>
<div>{{ shallowInfo.name }}</div>
</template>
<script>
import { isReactive, ref, shallowRef } from 'vue';
export default {
name: 'App',
setup() {
const info = ref({
name: 'Lance'
})
info.value.name = 'Lance1'
console.log(info.value); // Proxy { name: 'Lance1' }
console.log(isReactive(info.value)); // true

const shallowInfo = shallowRef({
name: 'GC'
});
setTimeout(() => {
// shallowInfo.value.name = 'GC1'; // shadowRef 会追踪 value 本身的变化(直接赋值新对象);但不会把value包装成reactive响应式对象
shallowInfo.value = { name: 'GC1' }; // 页面1s后变成 GC1
}, 1000);
// shallowInfo.value.name = 'GC1';
console.log(shallowInfo); // 普通值 { name: 'GC1' }
console.log(isReactive(shallowInfo)); // false

return {
shallowInfo
}
}
}
</script>
<template>
<div>{{ shallowInfo.name }}</div>
</template>
<script>
import { isReactive, ref, shallowRef, watchEffect } from 'vue';
export default {
name: 'App',
setup() {
const shallowInfo = shallowRef({
name: 'GC'
});

watchEffect(() => {
console.log(shallowInfo.value.name); // 监听不到
});
shallowInfo.value.name = 'GC1'; // 上边 watchEffect 监听不到这里的变化

return {
shallowInfo
}
}
}
</script>

triggerRef

  • 上边 shallowRef 的变化监听不到了,如果想要监听,就用 triggerRef 手动触发监听
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div>{{ shallowInfo.name }}</div>
</template>
<script>
import { isReactive, ref, shallowRef, triggerRef, watchEffect } from 'vue';
export default {
name: 'App',
setup() {
const shallowInfo = shallowRef({
name: 'GC'
});

watchEffect(() => {
console.log(shallowInfo.value.name); // 监听不到
});
shallowInfo.value.name = 'GC1'; // 上边 watchEffect 监听不到这里的变化
triggerRef(shallowInfo); // 手动触发更新: watchEffect 监听到了;template也更新了

return {
shallowInfo
}
}
}
</script>

🌈 computed

  • 返回一个不可变的响应式ref,computed 的返回值是从 Proxy 的 getter 中取的
  • 内部也是用 Proxy 实现的
  • 用法
    • computed(() => 'xxx')
    • computed({ get() {}, set() {} })
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>{{ sentence }}</div>
</template>

<script>
import { ref, computed } from 'vue';
export default {
name: 'App',
setup() {
const name = ref('Lance');
const sentence = computed(() => `欢迎${name.value}的到来`);
return {
sentence
}
}
}
</script>
<template>
<div>{{ sentence }}</div>
<div>{{ sentence2 }}</div>
</template>

<script>
import { ref, computed } from 'vue';
export default {
name: 'App',
setup() {
const name = ref('Lance');
const sentence = computed(() => `欢迎${name.value}的到来`);
const sentence2 = computed({
get() {
return `获取${name.value}`
},
set(val) {
console.log(`我被修改成了${val}`);
name.value = 'GC';
}
});
sentence2.value = 233;
return {
sentence,
sentence2
}
}
}
</script>

readonly

  • 深度设置属性的 readonly
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>
<div>123</div>
</template>
<script>
import { reactive, readonly, watchEffect } from 'vue';
export default {
name: 'App',
setup() {
const reactiveObj = reactive({
name: 'Lance',
info: {
grade: 120,
weight: 130
}
});
const readonlyReactiveObj = readonly(reactiveObj);

reactiveObj.name = 'GC';
console.log(reactiveObj); // 能够改变
readonlyReactiveObj.name = 'GC1'; // info 下的属性也无法改变
console.log(readonlyReactiveObj); // 无法改变

watchEffect(() => {
console.log(readonlyReactiveObj.name);
// 源对象reactiveObj改变,readonlyReactiveObj也会同步更新,而且会被 watchEffect 监听到
});
}
}
</script>

🌈 watchEffect

  • 首次加载会被立即执行一次
  • 自动收集 watchEffect 中的依赖
  • 依赖改变后,watchEffect 会被重新执行一次
  • 组件被卸载后会自动停止(unmounted)
  • watchEffect 返回 stop 函数,显示的执行能够立即停止 watchEffect 的监听
  • 执行时期
    • 首次执行:在组件 mounted 之前被调用
    • 后续执行:默认在组件 beforeUpdate ,也就是组件更新之前被调用
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
<template>
<div></div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
name: 'App',
setup() {
const count = ref(0);

const stop = watchEffect(() => {
console.log(count.value); // 0 2
});
setTimeout(() => {
stop();
// 此处显示调用后,就无法监听到下边 3s 后对 count.value 的改变了
}, 2000);

setTimeout(() => {
count.value = 2;
}, 1000);

setTimeout(() => {
count.value = 233; // 没能被 watchEffect 监听到,因为被显示 stop 了
}, 3000);
}
}
</script>

清除副作用

  • 有时我们会在 watchEffect 中做一些副作用操作
    • e.g. axios 异步数据数据、Promise、input 防抖搜索
    • 此刻传入 watchEffect 中的函数是个异步函数
    • 我们可以在 onInvalidate 回调中清除副作用
  • onInvalidate 执行时机
    • watch(副作用) 即将被执行时
    • watch 被 stop 停止
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
<template>
<div></div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
name: 'App',
setup() {
const count = ref(0);
// effect function(副作用函数) 指的是:
// async (onInvalidate) => {} 这个异步函数整体
// effect(副作用)指的是:
// const data = await Promise.resolve(count.value); 这个异步Promise
const stop = watchEffect(async (onInvalidate) => {
const data = await Promise.resolve(count.value);
console.log(data);

onInvalidate(() => {
// 可以在 onInvalidate 回调中拦截 上边 await 函数产生的副作用
// 即 onInvalidate 会在外层这个副作用函数(async (onInvalidate) => {})调用之前执行
console.log('onInvalidate is triggered');
});
});
setTimeout(() => {
count.value = 2;
}, 1000);
setTimeout(() => {
stop();
console.log('watchEffect is stopped');
}, 2000);
}
}
</script>

即使没有产生副作用,onInvalidate 也先于 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
<template></template>
<script>
import { ref, watchEffect } from 'vue';
export default {
name: 'App',
setup() {
const count = ref(0);
const stop = watchEffect((onInvalidate) => {
console.log(count.value);

onInvalidate(() => {
// 此回调也会先于 上边 log 所在的函数之前执行
console.log('onInvalidate is triggered');
});
});
setTimeout(() => {
count.value = 2;
}, 1000);
setTimeout(() => {
stop();
console.log('watchEffect is stopped');
}, 2000);
}
}
</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
<template></template>
<script>
import { ref, watchEffect } from 'vue';
export default {
name: 'App',
setup() {
const count = ref(0);
function getData() {
return new Promise((resolve, reject) => {
resolve(100);
});
}
watchEffect(async (onInvalidate) => {
console.log("count.value", count.value);
const data = await getData();
console.log("data", data);
onInvalidate(() => {
console.log('onInvalidate is triggered');
});
});
setTimeout(() => {
count.value = 233;
}, 1000);
}
}
</script>
  1. 先立即执行一次 watchEffect 打印 count.value 0,接着打印 data 100
  2. 然后1s后改变了count
  3. 在 watchEffect 执行之前先执行了 onInvalidate 中的回调 onInvalidate is triggered
  4. 然后才再次执行 watchEffect 打印 count.value 233,最后打印 data 100
  • 如果 watchEffect 产生了副作用,vue会自动捕获这个异步函数隐式返回的promise的错误
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></template>
<script>
import { ref, watchEffect } from 'vue';
export default {
name: 'App',
setup() {
const count = ref(0);
function getData() {
return new Promise((resolve, reject) => {
reject('err');
});
}
watchEffect(async (onInvalidate) => {
console.log("count.value", count.value);
const data = await getData();
console.log("data", data);
onInvalidate(() => {
console.log('onInvalidate is triggered');
});
});
setTimeout(() => {
count.value = 233;
}, 1000);
}
}
</script>

副作用刷新时机

当一个用户定义的副作用函数进入队列时,默认情况下,会在所有的组件 update 前执行

  • 也就是说,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
<template>
<div>{{ count }}</div>
</template>
<script>
import { ref, watchEffect, onBeforeUpdate } from 'vue';
export default {
name: 'App',
setup() {
const count = ref(0);
setTimeout(() => {
count.value = 2333;
}, 1000);
watchEffect(() => {
console.log(count.value);
});
onBeforeUpdate(() => {
console.log('onBeforeUpdate');
});
return {
count
}
}
}
</script>

如果想要在组件更新之后才调用,则给 watchEffect 追加一个参数,参数是对象,设置 flush 属性

  • flush
    • pre 默认值:组件更新前调用
    • post :组件更新后调用
    • sync :同步执行(用得少)
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>{{ count }}</div>
</template>
<script>
import { ref, watchEffect, onBeforeUpdate } from 'vue';
export default {
name: 'App',
setup() {
const count = ref(0);
setTimeout(() => {
count.value = 2333;
}, 1000);
watchEffect(() => {
console.log(count.value);
}, {
flush: 'post'
});
onBeforeUpdate(() => {
console.log('onBeforeUpdate');
});
return {
count
}
}
}
</script>
  • 首次执行 watchEffect ,是在组件 mounted 之前被调用
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
<template>
<div>{{ count }}</div>
</template>
<script>
import { ref, watchEffect, onBeforeUpdate, onMounted } from 'vue';
export default {
name: 'App',
setup() {
const count = ref(0);
setTimeout(() => {
count.value = 2333;
}, 1000);
watchEffect(() => {
console.log(count.value);
});
onMounted(() => {
console.log('onMounted');
});
onBeforeUpdate(() => {
console.log('onBeforeUpdate');
});
return {
count
}
}
}
</script>
  • 如果需要在 watchEffect 中使用到 DOM ,请在外表包裹一层 onMounted
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div ref="myRef"></div>
</template>
<script>
import { ref, watchEffect, onMounted } from 'vue';
export default {
name: 'App',
setup() {
const myRef = ref(null);

onMounted(() => {
watchEffect(() => {
console.log(myRef.value);
});
});
return {
myRef
}
}
}
</script>

watch debug

  • watchEffect 第二个参数对象中接收两个函数
    • onTrack 依赖被追踪时被调用
    • onTrigger 依赖变更导致 watch 副作用被触发时被调用
    • 要调试的时候就在这俩函数中打 debugger
    • 这俩函数只会在开发模式下生效,生产环境不生效
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>
<div>{{ count }}</div>
</template>
<script>
import { ref, watchEffect } from 'vue';
export default {
name: 'App',
setup() {
const count = ref(0);
setTimeout(() => {
count.value = 2333;
}, 1000);
watchEffect(() => {
console.log(count.value);
}, {
onTrack(e) { // 依赖项被追踪时调用
console.log('track', e);
},
onTrigger(e) { // 依赖更新导致watch副作用被调用时调用
debugger;
console.log('trigger', e);
}
});
return {
count
}
}
}
</script>

🌈 watch

  • 懒加载
    • 区别于 watchEffect ,首次加载不被监听
  • 接收两个函数
    • 第一个函数中: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
<template>
<div>{{ count }}</div>
</template>
<script>
import { ref, watch } from 'vue';
export default {
name: 'App',
setup() {
const count = ref(0);
setTimeout(() => {
count.value = 233;
}, 1000);
// 第一种写法
watch(() => {
return count.value; // 要监听的对象
}, (newVal, oldVal) => {
console.log(count.value);
console.log('newVal:', newVal, 'oldVal:', oldVal);
});
// 简写
watch(() => count.value, (newVal, oldVal) => {
console.log(count.value);
console.log('newVal:', newVal, 'oldVal:', oldVal);
});
return {
count
}
}
}
</script>

第二种写法:

  • 只支持 refreactive 数据只能用第一种方案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div>{{ count }}</div>
</template>
<script>
import { ref, watch } from 'vue';
export default {
name: 'App',
setup() {
const count = ref(0);
setTimeout(() => {
count.value = 233;
}, 1000);
// 第二种写法
watch(count, (newVal, oldVal) => {
console.log(count.value);
console.log('newVal:', newVal, 'oldVal:', oldVal);
});
return {
count
}
}
}
</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
<template>
<div></div>
</template>
<script>
import { reactive, watch } from 'vue';
export default {
name: 'App',
setup() {
const obj = reactive({
name: 'lance'
});
setTimeout(() => {
obj.name = 'GC';
}, 1000);
watch(() => {
return obj.name;
}, () => {
console.log(obj.name);
});
// 简写形式:
watch(() => obj.name, () => {
console.log(obj.name);
});
}
}
</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
<template></template>
<script>
import { ref, watch } from 'vue';
export default {
name: 'App',
setup() {
const count = ref(0);
const name = ref('张三');
setTimeout(() => {
count.value = 1;
name.value = '李四';
}, 1000);
watch(() => {
return [count.value, name.value];
}, ([newCount, newName], [oldCount, oldName]) => {
console.log({
'newCount': newCount,
'newName': newName,
'oldCount': oldCount,
'oldName': oldName
});
});
}
}
</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
<template>
<div>
<p>{{ count }}</p>
<p>{{ name }}</p>
<button @click="changeName">change</button>
</div>
</template>
<script>
import { ref, watch } from 'vue';
export default {
name: 'App',
setup() {
const count = ref(0);
const name = ref('张三');

const changeName = () => {
name.value = '李四';
}

watch(() => {
return [count.value, name.value];
}, ([newCount, newName], [oldCount, oldName]) => {
console.log(newName); // 李四
});

return {
count,
name,
changeName
}
}
}
</script>

与 watchEffect 共享的行为

watch 与 watchEffect共享停止侦听清除副作用 (相应地 onInvalidate 会作为回调的第三个参数传入)、副作用刷新时机侦听器调试行为。

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
<template>
<div>
<p>{{ count }}</p>
<p>{{ name }}</p>
<button @click="changeName">change</button>
</div>
</template>
<script>
import { onBeforeUpdate, ref, watch } from 'vue';
export default {
name: 'App',
setup() {
const count = ref(0);
const name = ref('张三');

const changeName = () => {
console.log('点击按钮后');
name.value = '李四';
}

watch(() => {
return [count.value, name.value];
}, ([newCount, newName], [oldCount, oldName]) => {
console.log(newName);
}, {
flush: 'post',
onTrack(e) {
console.log('onTrack', e);
},
onTrigger(e) {
console.log('onTrigger', e);
}
});

onBeforeUpdate(() => {
console.log('onBeforeUpdate');
// 默认先 watch 后 onBeforeUpdate
// flush: 'post' 之后, 先 onBeforeUpdate 后 watch
});

return {
count,
name,
changeName
}
}
}
</script>
  • onInvalidate 作为回调的第三个参数
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>
<p>{{ count }}</p>
<button @click="changeCount">change</button>
</div>
</template>
<script>
import { ref, watch } from 'vue';
export default {
name: 'App',
setup() {
const count = ref(0);

const changeCount = () => {
console.log('点击按钮后');
count.value++;
}

watch(() => {
return count.value;
}, (newCount, oldCount, onInvalidate) => {
console.log(newCount, oldCount);
onInvalidate(() => {
console.log('onInvalidate is triggered');
});
});

return {
count,
changeCount
}
}
}
</script>
  • 注意下方第一次点击后并没有触发 onInvalidate
  • 说明第一次才注册,第二次追踪依赖变化,再变化前执行
  • 显示调用 stop 函数
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
<template>
<div>
<p>{{ count }}</p>
<button @click="changeCount">change</button>
</div>
</template>
<script>
import { ref, watch } from 'vue';
export default {
name: 'App',
setup() {
const count = ref(0);

const changeCount = () => {
console.log('点击按钮后');
count.value++;
}

const stop = watch(() => {
return count.value;
}, (newCount, oldCount) => {
console.log(newCount, oldCount);
});

setTimeout(() => {
stop();
}, 5000);

return {
count,
changeCount
}
}
}
</script>

isProxy

  • 检查一个对象是否是被 reactive 或者 readonly 代理的对象

      • 普通对象为 false
    • 自己 new 的 Proxy 对象为 false
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
<template>

</template>

<script>
import { reactive, readonly, isProxy } from 'vue';
export default {
name: 'App',
setup() {
const state = reactive({
name: 'Lance'
});
const state2 = readonly({
name: 'GC'
});
const state3 = {
name: 'Sherry'
};
const state4 = new Proxy({
name: 'Summer'
}, {});
console.log("reactive对象为:", isProxy(state));
console.log("readonly对象为:", isProxy(state2));
console.log("普通对象为:", isProxy(state3));
console.log("自己new的Proxy对象为:", isProxy(state4));
}
}
</script>

isReactive

  • 是否是通过 reactive 包装代理的对象
  • 注意
    • readonly 包裹的普通对象为 false
    • readonly 包裹的 reactive 对象为 true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template></template>
<script>
import { reactive, isReactive, readonly, isProxy, isReadonly } from 'vue';
export default {
name: 'App',
setup() {
const obj = reactive({
name: 'Lance'
});
const obj2 = readonly({
name: 'GC'
});
const obj3 = readonly(obj);
const obj4 = reactive(1);
console.log(isProxy(obj), isReactive(obj), isReadonly(obj));
console.log(isProxy(obj2), isReactive(obj2), isReadonly(obj2));
console.log(isProxy(obj3), isReactive(obj3), isReadonly(obj3));
console.log(isProxy(obj4), isReactive(obj4), isReadonly(obj4));

}
}
</script>

shallowReactive

  • reactive 其实可以理解为 deepReactive ,即深度代理
  • shallowReactive 只代理最外层,类似浅拷贝
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
<template>
<div>
<p>{{ name }}</p>
<p>{{ info.grade }}</p> <!-- 此处无法监听到 button 改变 grade 后的更新 -->
<button @click="change">change</button>
</div>
</template>
<script>
import { reactive, toRefs, shallowReactive, watchEffect } from 'vue'
export default {
name: 'App',
setup() {
const state = shallowReactive({
name: 'Lance', // 这一层能够监听到
info: {
grade: 100, // 第二层开始就无法监听到了
}
});
const change = () => {
state.info.grade += 10;
}
watchEffect(() => {
console.log(state.info.grade); // 这里无法监听到
});
return {
...toRefs(state),
change
}
}
}
</script>

shallowReadonly

  • 区别于 readonly 的深度遍历,shallowReadonly 只会 readonly 对象的第一层属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template></template>
<script>
import { readonly, shallowReadonly, isReadonly } from 'vue';
export default {
name: 'App',
setup() {
const obj = readonly({
name: 'Lance',
info: {
grade: 100
}
});
const shallowObj = shallowReadonly({
name: 'Lance',
info: {
grade: 100
}
});
console.log(isReadonly(obj), isReadonly(shallowObj)); // true true
console.log(isReadonly(obj.info), isReadonly(shallowObj.info)); // true false
}
}
</script>

toRaw

  • 把 reactive 或者 readonly 变成最初的普通对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template></template>
<script>
import { reactive, toRaw } from '@vue/reactivity'
export default {
name: 'App',
setup() {
const obj = {
name: 'Lance'
};
const reactiveObj = reactive(obj);
const rawObj = toRaw(reactiveObj);
console.log(rawObj);
console.log(rawObj === obj); // 和原对象是一个对象
}
}
</script>

markRaw

  • 标记一个对象,使其永远不会转换为 proxy。返回对象本身。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template></template>
<script>
import { markRaw, reactive } from 'vue';
export default {
name: 'App',
setup() {
const obj = {
name: 'Lance',
info: {
grade: 100
}
};
const rawObj = markRaw(obj);
console.log(obj, rawObj);
console.log(obj === rawObj);
// 等于原对象,且给原对象添加了一个 __v_skip: true 标识,用来标记无法被 reactive

const proxyObj = reactive(rawObj);
console.log(proxyObj); // 返回原对象
}
}
</script>
  • markRow 和 shallowXXX API 默认都只作用于对象的第一层对象,第二层就失效了
    • 例如
      • markRow 只作用于目标对象的第一层
      • shallowReactive 只对目标对象的第一层有响应式作用,深层就失去响应式了
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
<template></template>
<script>
import { markRaw, reactive } from 'vue';
export default {
name: 'App',
setup() {
const obj = {
name: 'Lance',
info: {
grade: 100
}
};
const rawObj = markRaw(obj);

const proxyObj = reactive({
info: rawObj.info
});
console.log(proxyObj, rawObj);
// rawObj 会被标记为 _v_skip: true
// 但旗下 info 对象并没有被标记为 _v_skip: true



console.log(proxyObj.info === obj.info);
}
}
</script>