reactive
- 接收一个对象,返回一个响应式的被Proxy包装过的对象(返回的对象 不等于 源对象)
- 等同于 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
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
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
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
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
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>
|
- 先立即执行一次 watchEffect 打印 count.value 0,接着打印 data 100
- 然后1s后改变了count
- 在 watchEffect 执行之前先执行了 onInvalidate 中的回调
onInvalidate is triggered
- 然后才再次执行 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>
|
第二种写法:
- 只支持
ref
,reactive
数据只能用第一种方案
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>
|
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>
|
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
- 说明第一次才注册,第二次追踪依赖变化,再变化前执行
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
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>
|