持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情
上一节我们讲述了如何在不使用构建工具的情况下使用vue3,基于上一节的代码,我们来学习一下vue3的响应式系统Api
响应式系统介绍
在vue3的源码中,响应式系统使用单独的一个模块,路径在packages/reactivity下。
响应式以系统是i一组具有响应式特性的函数API,都是以函数形式提供的。
也就是说我们是可以在项目中单独使用这一部分API,让我们的数据具备响应式。
主要包含以下几个api:
- reactive
- ref
- computed
- readonly
- watchEffect
- watch
为了更好的演示,我们还是在vue3的setup环境下进行讲述。
reactive
reactive()函数接收一个普通对象,返回该普通对象的响应式代理对象。- 响应式转换是“深层的”:会影响对象内部所有嵌套的属性。
<div id="app">
<button @click="increment">
Count is: {{ state.count }}
</button>
</div>
<script>
const { createApp, reactive } = Vue
createApp({
setup() {
const state = reactive({ count: 0 });
function increment() {
state.count++;
}
// 不要忘记同时暴露 increment 函数
return {
state,
increment,
};
},
}).mount('#app')
</script>
ref
ref()函数接收一个参数值,返回一个响应式的数据对象。该对象只包含一个指向内部值的 .value 属性- 如果传入 ref 的是一个对象,将调用 reactive 方法进行深层响应转换。
<div id="app">
<button @click="increment">
Count is: {{ count }}
</button>
</div>
<script>
const { createApp, ref } = Vue
createApp({
setup() {
const count = ref(0);
function increment() {
count.value++;
}
return {
count,
increment,
};
},
}).mount('#app')
</script>
注意:当 ref 作为渲染上下文的属性返回(即在setup() 返回的对象中)并在模板中使用时,它会自动解套,无需在模板内额外书写 .value
- reactive包裹ref
在reactive对象中访问ref时,无需通过.value属性,它会自动展开
const count = ref(0)
const state = reactive({
count,
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
注意:当嵌套在 reactive Object 中时,ref 才会解套。从 Array 或者 Map 等原生集合类中访问 ref 时,不会自动解套:
const arr = reactive([ref(0)])
// 这里需要 .value
console.log(arr[0].value)
const map = reactive(new Map([['foo', ref(0)]]))
// 这里需要 .value
console.log(map.get('foo').value)
computed
computed()函数用来创建计算属性,函数的返回值是一个 ref 的实例
getter函数
<div id="app">
<div>{{ count }}</div>
<div>{{plusOne}}</div>
</div>
<script>
const { createApp, ref, computed } = Vue
createApp({
setup() {
const count = ref(1);
/**
*!只读的计算属性
*/
// 根据count创建一个响应式的计算属性
const plusOne = computed(() => {
// get函数
return count.value + 1;
});
console.log(plusOne.value);
return {
count,
plusOne,
};
},
}).mount('#app')
</script>
当我们只传入一个函数时,此函数为getter函数,也就是读取值,返回一个默认不可手动修改的 ref 对象。
getter和setter同时使用
- 我们也可以传入一个拥有 get 和 set 函数的对象,创建一个可手动修改的计算状态
<div id="app">
<div>{{ count }}</div>
<button @click="increment">{{plusOne}}</button>
</div>
<script>
const { createApp, ref, computed } = Vue
createApp({
setup() {
const count = ref(1);
function increment() {
plusOne.value++
}
const plusOne = computed({
get: () => {
return count.value + 1;
},
set: (val) => {
count.value = val * 2;
},
});
console.log(plusOne.value);
return {
count,
plusOne,
increment
};
},
}).mount('#app')
</script>
readonly
readonly()函数接收一个对象(普通或响应式),返回一个原始对象的只读代理对象
const state = reactive({
count: 1,
});
const proxy = readonly(state);
console.log(state.count);
console.log(proxy.count);
//! 代理对象只读的,不允许修改
// proxy.count++
//! 修改原始对象时代理对象也会响应式变化
state.count++;
console.log(state.count);
console.log(proxy.count);
watchEffect
watchEffect()函数接收一个函数作为参数,并立即执行该函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
const count = ref(1);
/**
* 1. 先立即执行传入的函数
* 2. 当函数内依赖的数据发生变化时,会再次执行函数
*/
watchEffect(() => {
console.log(count.value);
});
onMounted(() => {
console.log('mounted');
})
setTimeout(() => {
count.value++;
}, 3000);
注意:第一次进入页面时,
watchEffect在onMounted之前执行,如果我们想获取模版refs或者dom元素,需要在生命周期之后执行watchEffect
onMounted(() => {
watchEffect(() => {
// 在这里可以访问到 DOM 或者 template refs
})
})
停止监听
- 在
setup函数或者声明周期中调用watchEffect时, 侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。 - 但是有些时候我们想要手动停止监听,我们可以通过返回值来做操作。
const stop = watchEffect(() => {
/* ... */
})
// 之后
stop()
清除副作用
如果我们想在watchEffect中异步获取数据,但是当我们想在组件销毁时取消异步获取数据,减少无意义的请求怎么做呢。我们可以借接收一个 onInvalidate 函数作入参, 用来注册清理失效时的回调。
watchEffect((onInvalidate) => {
const token = performAsyncOperation(id.value)
onInvalidate(() => {
// id 改变时 或 停止侦听时
// 取消之前的异步操作
token.cancel()
})
})
watch
watch()函数用来监视数据的变化,从而触发特定的操作,等同于 vue 2.x 中的this.$watch
监听单个数据源
- 监听
ref数据时,之间监听即可,不需要使用.value
//! 1. 监视ref数据源
const count = ref(1);
watch(count, (newVal, oldVal) => {
console.log(oldVal);
console.log(newVal);
});
setTimeout(() => {
count.value = 6;
}, 3000);
- 监听
reactive中单个数据时,需要使用函数的形式来进行监听
//! 2. 监视rective数据源
const state = reactive({
count: 1,
});
// 监视state.count, 必须以函数形式定义
watch(
() => state.count,
(newVal, oldVal) => {
console.log(oldVal);
console.log(newVal);
}
);
setTimeout(() => {
state.count = 8;
}, 3000);
监听多个数据源
- 我们也可以使用数组形式,监听多个数据源。
//! 3.监视多个数据源
const name = ref("tom");
const age = ref("18");
watch([name, age], ([newName, newAge], [oldName, oldAge]) => {
console.log(oldName, newName);
console.log(oldAge, newAge);
});
setTimeout(() => {
name.value = "张三";
age.value = "11";
}, 3000);
总结
以上就是关于响应式系统的基本介绍,更多内容还需要在实战中继续深入研究。