Vue3-组合式API学习之响应式系统API
学习来源响应系统API1.reactive接收一个普通对象然后返回该普通对象的响应式代理。等同于2.x的Vue.observable();实例:import {reactive} from 'vue';setup(){const state = reactive({count : 0})return state}reactive响应式转换是’深层的’,会影响对象内部所有嵌套的属性。基于ES2015
响应系统API
1.reactive
接收一个普通对象然后返回该普通对象的响应式代理。等同于2.x的Vue.observable();
实例:
import {reactive} from 'vue';
setup(){
const state = reactive({
count : 0
})
return state
}
reactive响应式转换是’深层的’,会影响对象内部所有嵌套的属性。基于ES2015的Proxy实现,返回的代理对象不等于原始对象。建议仅使用代理对象而避免依赖原始对象。
2.ref
接收一个参数值并返回一个响应式且可改变的ref对象。ref对象拥有一个指向内部值的单一属性.value。
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
如果传入ref的值是一个对象,将调用reactive方法进行深层响应转换。
2.1 模板中访问
当ref作为渲染上下文的属性返回(即在setup()返回的对象中)并在模板中使用时,它会自动解构,无需在模板内额外书写.value。
<template>
<div class="home">
<button>{{count}}</button>
</div>
</template>
<script>
import { ref, reactive } from 'vue';
export default {
setup() {
const count = ref(0);
return {
count
}
}
}
</script>
如果传入ref的值是一个对象,将调用reactive方法进行深层响应转换。例如
<template>
<div class="home">
<button>{{count}}</button>
</div>
</template>
<script>
import { ref, reactive } from 'vue';
export default {
setup() {
const count = ref({
name:'balabala',
sex:'female'
});
const state = reactive({
count,
})
console.log(state.count);
state.count.name='xixixi';
console.log(count.value);
return {
count,
state,
}
}
}
</script>
2.2 类型标注
有时我们可能需要为ref做一个较为复杂的类型标注。我们可以通过在调用ref时传递泛型参数来覆盖默认推导:
<template>
<div class="home">
<button>{{foo}}</button>
</div>
</template>
<script lang='ts'>
import { ref } from 'vue';
export default {
setup() {
const foo = ref<string | number>('foo') // foo 的类型: Ref<string | number>
console.log(foo, "修改前")
foo.value = 123 // 能够通过!
console.log(foo, "修改后")
foo.value=false;
console.log(foo, "修改后后后") // 报错,类型不符合
return {
foo,
}
}
}
</script>
3.computed
3.1 传入一个getter函数,返回一个默认不可手动修改的ref对象
<template>
<div class="home">
<button @click="addNum">当前值为:{{count}},双倍值为:{{doubleCount}}</button>
</div>
</template>
<script lang='ts'>
import { ref,computed } from 'vue';
export default {
setup(){
const count = ref(2);
// computed中传入例如一个getter函数
const doubleCount = computed(()=>{
return count.value * 2
})
function addNum(){
count.value++
}
console.log("double后的值为:",doubleCount.value);
console.log("computed的值可以操作吗?",doubleCount.value++); // 报错,computed函数返回ref对象这个不可修改
return {
count,
doubleCount,
addNum,
}
}
}
</script>
3.2 传入一个拥有get和set函数的对象,创建一个可手动修改的计算状态
<template>
<div class="home">
<button @click="changeCount">当前值为:{{count}}</button>
</div>
</template>
<script lang='ts'>
import { ref,computed } from 'vue';
export default {
setup(){
const count = ref(0);
const plusOne = computed({
get:()=> count.value +1,
set:(val)=>{
count.value = val -1
}
})
function changeCount(){
plusOne.value = 2;
console.log(count.value) // 1
}
return {
changeCount,
count,
}
}
}
</script>
因为computed中有set方法,所以在计算时会直接调用set方法设置值,当我们plusOne.value赋值为2时,计算时set中val参数的值为2,计算结果为1;
4.readonly
传入一个对象(响应式或普通)或ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的。
<template>
<div class="home">
<button @click="changeCount">当前值为:{{original.count}}</button>
</div>
</template>
<script lang='ts'>
import { reactive,computed,watchEffect,readonly } from 'vue';
export default {
setup(){
const original = reactive({
count:0
});
const freezeCount = readonly(original) // freezeCount只读,修改会报错
function changeCount(){
original.count++ ; // 原始值修改
console.log(original.count)
// freezeCount.count++; //只读值修改 // !!!报错
}
watchEffect(()=>{
console.log(freezeCount.count,"只读变化不能修改") // 当值发生变化时,打印冻结的值
})
return {
original,
freezeCount,
changeCount,
}
}
}
</script>
5.watchEffect
立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。当watchEffect在组件的setup()函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在卸载时自动停止。
<template>
<div class="home">
<button @click="changeCount">当前值为:{{count}}</button>
</div>
</template>
<script lang='ts'>
import { ref,watchEffect } from 'vue';
export default {
setup(){
const count = ref(0);
const stop = watchEffect(()=>{
console.log(count.value,"当前count值");
setTimeout(()=>{
count.value++; // watchEffect中的count值发生变化
},1000)
})
stop(); // 如果不停止,将一直数量变化下去,加入这句话,在执行一次后停下
return {
count,
}
}
}
</script>
5.1 清除副作用
有时副作用函数会执行一些异步的副作用,这些相应需要在其失效时清除(即完成之前状态已经改变了)。所以侦听副作用传入的函数可以接受一个onInvalidate函数作入参,用来注册清理失效时的回调,当以下情况发生时,这个失效回调会被触发:
- 副作用即将重新执行时
- 侦听器被停止(如果在setup()或生命周期钩子函数中使用了watchEffect,则在卸载组件时)
watchEffect((onInvalidate) => {
const token = performAsyncOperation(id.value)
onInvalidate(() => {
// id 改变时 或 停止侦听时
// 取消之前的异步操作
token.cancel()
})
})
我们之所以是通过传入一个函数去注册失效回调,而不是从回调返回它,是因为返回值对于异步错误处理很重要。在执行数据请求时,副作用函数往往是一个异步函数,我们知道异步函数都会隐式地返回一个Promise,但是清理函数必须要在Promise被resolve之前被注册。另外,Vue依赖这个返回的Promise来自动处理Promise链上的潜在错误。
5.2 副作用刷新时机
Vue的响应式系统会缓存副作用函数,并异步地刷新他们,这样可以避免在同一个tick中多个状态改变导致的不必要的重复调用。在核心的具体实现中,组件的更新函数也是一个被侦听的副作用。当一个用户定义的副作用函数进入队列时,会在所有的组件更新后执行:
eg:watchEffect监听时机
<template>
<div>{{ count }}</div>
</template>
<script>
export default {
setup() {
const count = ref(0)
watchEffect(() => {
console.log(count.value)
})
return {
count,
}
},
}
</script>
在这个例子中:
- count会在初始运行时同步打印出来
- 更改count时,将在组件更新后执行副作用
请注意,初始化运行是在组件mounted之前执行的,因此,如果你希望在编写副作用函数时访问DOM(或模板ref),请在onMounted钩子中进行:
onMounted(() => {
watchEffect(() => {
// 在这里可以访问到 DOM 或者 template refs
})
})
如果副作用需要同步或在组件更新之前重新运行,我们可以传递一个拥有flush属性的对象作为选项(默认为’post’):
// 同步运行
watchEffect(
() => {
/* ... */
},
{
flush: 'sync',
}
)
// 组件更新前执行
watchEffect(
() => {
/* ... */
},
{
flush: 'pre',
}
)
5.3 侦听器调试
onTrack 和 onTrigger 选项可用于调试一个侦听器的行为。
- 当一个reactive对象属性或一个ref作为依赖被追踪时,将调用onTrack
- 依赖项变更导致副作用被触发时,将调用onTrigger
- onTrack和onTrigger仅在开发模式下生效
6.watch
watch需要侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源变更时才执行回调。
对比watchEffect,watch允许我们:
- 懒执行副作用
- 更明确哪些状态的改变会触发侦听器重新运行副作用
- 访问侦听状态变化前后的值
6.1 侦听单个数据源
侦听器的数据源可以是一个拥有返回值的getter函数,也可以是ref
<template>
<div class="home">
<button @click="changeCount">当前值为:{{count}}</button>
</div>
</template>
<script lang='ts'>
import { ref, watch,reactive } from 'vue';
export default {
setup() {
const count = ref(0);
const state = reactive({
count:0,
})
watch(
()=>count.value, // 被watch的值,这个值发生变化才会执行下面的这个函数
(count, prevCount) => {
console.log(count, prevCount)
}
)
watch(count, () => { // 如果被检测的值是一个变量可以直接监听,如果还要.其他的值则需要使用()=>这种方式
console.log(count.value, "变化后的count值")
})
function changeCount() {
count.value++;
}
return {
state,
count,
changeCount,
}
}
}
</script>
6.2 侦听多个数据源
watcher 也可以使用数组来同时侦听多个源:
<template>
<div class="home">
<button @click="changeCount">当前值count为:{{count}}</button>
<button @click="changeState">当前值state为:{{state.count}}</button>
<button @click="changeAll">修改两个</button>
</div>
</template>
<script lang='ts'>
import { ref, watch,reactive } from 'vue';
export default {
setup() {
const count = ref(0);
const state = reactive({
count:0,
})
// 检测多个数据时,用数组形式将变量放在一起,变化前后的变量们
watch([count,state],([count,state],[prevCount,prevState])=>{
console.log('改变后的两个值',[count,state])
console.log('改变前的两个值',[prevCount,prevState])
})
function changeAll(){
count.value++;
state.count+=2;
}
function changeCount() {
count.value++;
}
function changeState(){
state.count+=2;
}
return {
state,
count,
changeCount,
changeState,
changeAll,
}
}
}
</script>
watch侦听数据,格式为watch(第一个参数为被检测的数据,第二个参数为数据发生变化后执行的回调函数)。
6.3 与watchEffect共享的行为
watch 和 watchEffect在停止侦听,清除副作用(相应地onInvalidate会作为回调的第三个参数传入),副作用刷新时间和侦听器调试等方面行为一致。
更多推荐



所有评论(0)