类似于 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>
ref和reactive都类似于vue2中的return data() {},都是负责生成响应式数据reactive不能处理简单的数据,ref虽然可以处理所有数据,但是ref必须通过.value修改数据- 在功能上,
ref完全可以覆盖reactive的使用场景,所以不妨ref一把梭,就如同 API 使用post一把梭一样(当然这只是一个玩笑,API 还是推荐使用 RESTful 风格,不过在 Vue3的使用场景中,ref的应用场景显然是reactive的超集)
和 Vue2 基本一致的计算属性 computed
接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过
.value暴露 getter 函数的返回值。它也可以接受一个带有get和set函数的对象来创建一个可写的 ref 对象。
- 计算属性主要的作用就是根据一个数据计算得到一个新的数据,除了计算之外的事情,就不要做了。
- 虽然可以创建一个可写的
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
- 第一个参数是侦听器的源,源往往是一个响应式对象,或者是一个由响应式对象组成的数组
- 第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数,当监听多个源时,回调函数的前两个参数传入数组
- 第三个可选的参数是一个对象,可选值如下
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>
watch 的 deep 执行深度遍历
如果想让回调在深层级变更时也能触发,你需要使用
{ deep: true }强制侦听器进入深层级模式。
- 通过
watch监听的ref对象默认是浅层侦听的,直接修改嵌套对象的属性并不能触发回调,需要手动开启 deep - 通过
watch监听的reactive对象默认是开启深度侦听 - 在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象
如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象
<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>