Vue3 组合式API -- 响应式核心

154 阅读4分钟

类似于 Vue2 中的 return data() 的 ref 和 reactive

因为 vue2 采用选项式 API,所有的数据都包含在一个 data() 中返回,vue3 为了支持组合式 API,就必须有一种可以一次返回少量响应式数据的方案。

ref

接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value

ref 可以接收一个简单的类型,也可以是一个对象,返回一个响应式对象,需要从 vue 中引入 ref ,如果想要修改这个对象的值,需要使用 .value 修改。

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

  const count = ref(0);
  const addCount = () => {
    count.value ++;
  }
</script>

<template>
  <div>
    {{ count }}
    <button @click="addCount">add</button>
  </div>
</template>

reactive

返回一个对象的响应式代理。

reactive 只能传入一个对象,需要 从 vue 中引入 reactive

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

  const state = reactive({ count:0 });
  const addCount = () => {
    state.count ++;
  }
</script>

<template>
  <div>
    {{ state.count }}
    <button @click="addCount">add</button>
  </div>
</template>
  1. refreactive 都类似于 vue2 中的 return data() {},都是负责生成响应式数据
  2. reactive 不能处理简单的数据,ref 虽然可以处理所有数据,但是 ref 必须通过 .value 修改数据
  3. 在功能上, ref 完全可以覆盖 reactive 的使用场景,所以不妨 ref 一把梭,就如同 API 使用 post 一把梭一样(当然这只是一个玩笑,API 还是推荐使用 RESTful 风格,不过在 Vue3的使用场景中,ref 的应用场景显然是 reactive 的超集)

和 Vue2 基本一致的计算属性 computed

接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。

  1. 计算属性主要的作用就是根据一个数据计算得到一个新的数据,除了计算之外的事情,就不要做了。
  2. 虽然可以创建一个可写的 ref 对象,但是身为计算属性,在绝大多数的场景下都应该避免直接修改它
<script setup>
  import { ref,computed } from 'vue';

  const list = ref([1,2,3,4,5]);
  const filterList = computed(() => {
    return list.value.filter(item => item > 2);
  })

  setTimeout(() => {
    list.value = [1,2,3,4,5,6,7];
  }, 2000)
</script>

<template>
  <div>
    <div>{{ list }}</div>
    <div>{{ filterList }}</div>
  </div>
</template>

监听响应数据的 watch

监听一个或多个响应数据,在相应数据变化时执行回调函数

function watch<T>( 
 source: WatchSource<T>, 
 callback: WatchCallback<T>, 
 options?: WatchOptions 
): StopHandle
  1. 第一个参数是侦听器的,源往往是一个响应式对象,或者是一个由响应式对象组成的数组
  2. 第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数,当监听多个源时,回调函数的前两个参数传入数组
  3. 第三个可选的参数是一个对象,可选值如下
  • deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调
  • immediate:在侦听器创建时立即触发回调,第一次调用时旧值是 undefined
  • flush:调整回调函数的刷新时机。
  • onTrack / onTrigger:调试侦听器的依赖

监听单个数据源

<script setup>
import { ref, watch } from 'vue'
const count = ref(0);
const setCount = () => {
  count.value++;
}

watch(count, (newVal, oldVal) => {
  console.log('count变化', newVal, oldVal);
})
</script>

<template>
  <div>
    <button @click="setCount">{{ count }}</button>
  </div>
</template>

监听多个数据源

<script setup>
import { ref, watch } from 'vue'
const count = ref(0);
const from  = ref(100);
const setCount = () => {
  count.value++;
  from.value--;
}
// 虽然这里监听了两个数据源,但是是通过同一个函数触发了两个数据源的变化。也可以单独写函数控制单独的数据源
watch([count, from], ([countNew, fromNew], [countOld, fromOld]) => {
  console.log('count变化', countNew, countOld);
  console.log('from变化', fromNew, fromOld);
})
</script>

<template>
  <div>
    <button @click="setCount">{{ count }} + {{ from }} = 100</button>
  </div>
</template>

immediate 立即触发

watch 先执行一次,有点像页面类似 document.ready

<script setup>
import { ref, watch } from 'vue'
const count = ref(0);
const setCount = () => {
  count.value++;
}

watch(count, (newVal, oldVal) => {
  console.log('count变化', newVal, oldVal);
}, {
  immediate:true
})
</script>

<template>
  <div>
    <button @click="setCount">{{ count }}</button>
  </div>
</template>

image.png

watch 的 deep 执行深度遍历

如果想让回调在深层级变更时也能触发,你需要使用 { deep: true } 强制侦听器进入深层级模式。

  1. 通过watch 监听的 ref 对象默认是浅层侦听的,直接修改嵌套对象的属性并不能触发回调,需要手动开启 deep
  2. 通过 watch 监听的 reactive 对象默认是开启深度侦听
  3. 在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象

如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象

<script setup>
import { ref, watch } from 'vue'
const state = ref({ count:0 });
const setCount = () => {
  state.value.count++;
}

// 在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象,此时打印的newVal.count和oldVal.count是一致的
watch(state, (newVal, oldVal) => {
  console.log('count变化', newVal.count, oldVal.count)
}, {deep: true});
</script>

<template>
  <div>
    <button @click="setCount">{{ state.count }}</button>
  </div>
</template>

精确监听对象的某个属性变化

deep 开启强制深度遍历会存在性能损耗,所以能不用就不要用,以下这个例子监听了对象的 sum 属性,只需要对 watch 传入两个函数,第一个函数返回 监听对象的值, 第二个函数可以 做一些其他处理

<script setup>
import { ref, watch } from 'vue'
const state = ref({ count:0, sum: 10});
const setCount = () => {
  state.value.count++;
}
const setSum = () => {
  state.value.sum++;
}

watch(
  () => state.value.sum,
  () => {
    console.log("sum变化", state.value.sum)
  }
)
</script>

<template>
  <div>
    <button @click="setCount">{{ state.count }}</button>
    <button @click="setSum">{{ state.sum }}</button>
  </div>
</template>