ref
ref 是用来创建响应式引用的主要方法。它通常用于基本数据类型(如字符串、数字、布尔值、对象、数组等)的响应式包装。在模板中可以直接使用,但在 JavaScript 中需要通过 .value 属性来访问或修改它的值
1、ref具有深层次响应式特点,主要针对对象;
2、通过shallowRef创建的响应式,不会深层递归,不具备深层响应式特点;
import { ref } from 'vue'; const count = ref(0); const userName = '洛可可白'; // 在 JavaScript 中访问 console.log(count.value); // 0 count.value++; // 增加计数 // 在模板中访问 // <div>{{ count }}</div> // ref也可以用于获取dom元素 <div><div ref="myDiv"></div> const myDiv = ref(null); myDiv.value.innerHTML = userName;reactive
reactive 是用来创建响应式对象的方法,适用于处理对象和数组。reactive对象的属性可以在模板和 JavaScript 中直接访问和修改,不需要 .value
1、reactive具有深层次响应式特点;
2、通过shallowReactive创建的响应式,不会深层递归,不具备深层响应式特点;
3、使用reactive创建对象时不要去直接替换reactive关联的对象,会丢失响应式;
4、对reactive的数据进行对象解构时,解构的数据也会丢失响应式;
5、如果将ref作为reactive创建对象的属性,获取该属性值时,不需要解包(无需.value);但是如果将ref作为shallowReactive创建对象的属性时,不会自动解包。
import { reactive } from 'vue'; const state = reactive({ count: 0, userName: '洛可可白' }); // 在 JavaScript 中访问和修改 console.log(state.count); // 0 state.count++; // 增加计数 // 在模板中直接访问 // <div>{{ state.count }}</div> // <div>{{ state.userName }}</div>ref对比reactive
vue中的响应式是通过Proxy来实现的,Proxy只能拦截对象,无法拦截原始值;那如果需要把原始值转成响应式如何处理?
1、用户自己把原始值包装成对象,再使用reactiveApi;
2、框架层面帮你处理成对象(使用refApi),ref有两种处理方式:
A、如果是原始值,使用Object.defineProperty来实现;
B、如果是对象,使用reactiveApi来实现;
C、reactive响应式内部是由Proxy来实现;
响应式其它API
toRef、toRefs、unRef、readonly、isRef、isReactive、isProxy、isReadonly
import { isReactive, reactive, toRef, isRef, unref, toRefs, shallowReactive, shallowReadonly, readonly, isProxy, } from 'vue' const state = reactive({ count: 1, obj: { value: 1, }, }) // 将响应式对象属性转化为ref const countRef = toRef(state, 'count') // => const countRef = ref(state.count) // 将整个reactive对象的所有属性都转化为ref对象 const refs = toRefs(state) // 取值 => const count = refs.count.value // 判断一个对象是否是ref isRef(countRef) // true // 解包ref对象,如果不是ref直接返回值 unref(countRef) // 1 // 判断是否是reactive isReactive(state) // true isReactive(state.obj) // true reactive是深层次响应式,所以下层对象也是reactive对象 // 创建只读代理 只能获取不能改值 const readObj = readonly(state || countRef) // isProxy 检查一个对象是否由reactive、readonly、shallowReactive、shallowReadonly创建的代理 // 因为ref内部是用了两种方式,所以它并不是单一的Proxy代理实现 isProxy(state)customRef
customRef 是 Vue 3 响应式系统暴露的一个底层 API,它允许开发者创建具有完全自定义依赖追踪和触发更新逻辑的 ref;customRef 函数接收一个工厂函数,这个工厂函数需要返回一个包含 get 和 set 方法的对象,而这两个方法会收到 track 和 trigger 这两个关键函数作为参数
import{customRef}from'vue';constmyCustomRef=customRef((track,trigger)=>{// 内部可以定义任何你想要的变量letvalue='initial value';return{get(){track();// 依赖收集returnvalue;},set(newValue){value=newValue;trigger();// 触发更新}};});customRef 实现防抖
这是 customRef 最经典的应用场景:创建一个值在频繁更改时(如输入框输入),只会在最后一次更改后的特定时间后才触发更新
效果:你在输入框中快速打字时,下方的显示不会实时变化,只会在你停止输入 500 毫秒后更新为最终内容。
<template><input v-model="debouncedText"placeholder="试试快速输入..."/><p>防抖后的值:{{debouncedText}}</p></template><script setup>import{customRef}from'vue';// 1. 定义一个通用的防抖 customRef 函数functionuseDebouncedRef(value,delay=500){returncustomRef((track,trigger)=>{const_value=valueconst_debounce=debounce((val)=>{_value=valtrigger()},delay)return{get(){track();// 追踪依赖return_value;},set(newValue){_debounce(newValue)}};});}// 2. 使用它constdebouncedText=useDebouncedRef('',500);</script>v-model、defineModel(vue3.4)
v-model 既可以实现数据到表单元素之间的双向绑定,也可以用于父子组件之间的数据双向绑定
<!--使用场景一:基本表单组件(将组件实例上的searchText绑定给input的value属性)--><input v-model="searchText"/><!--使用场景二:父子组件双向绑定--><!--当父组件中pageNum发生变化时,子组件会自动更新currentPage的值,同样当子组件的currentPage发生变化时,父组件也会自动更新pageNum的值--><!--父组件--><pagination v-model:page="pageNum"/><!--子组件-->constprops=defineProps({page:{type:Number,default:1}});constemit=defineEmits(['update:page']);constcurrentPage=computed<number|undefined>({get:()=>props.page,set:value=>{emit('update:page',value);}});defineModel仅用于父子组件之间的数据双向绑定,它只是一个语法糖,内部还是用的props取值以及自定义事件来完成父组件数据的更新,并没有打破单向数据流
<!--父组件--><Home v-model:count1="state.count"v-model:count2="_count1"name="张三"></Home><!--子组件-->constcount1=defineModel('count1')constcount2=defineModel('count2')functionupdate(){count1.value++;count2.value++;}computed
computed 用于创建计算属性,这些属性的值是基于其他响应式数据源派生出来的。计算属性是惰性求值的,只有当它们的依赖响应式数据发生改变时才会重新计算,对比模板中直接使用函数来实现,它的性能更好
computed值也可以修改,但是computed的设计思想只用来获取值,所以不建议进行修改操作
import { ref, computed } from 'vue' const count = ref(0) const doubleCount = computed(() => count.value * 2) // computed的修改使用 import { computed, isReactive, reactive, ref } from 'vue' const state = reactive({ count: 1, obj: { value: 1, }, }) const count = ref(0) const newCount = computed({ get(value) { return state.count + 10 }, set(value) { count.value = value }, }) setTimeout(() => { newCount.value = newCount.value + 1 }, 2000)watch
watch 用于观察响应式数据的变化,并在数据变化时执行特定的函数。
1、watch的执行顺序优于computed
2、watch可以监听ref、reactive、computed,以及Getter函数(监听函数返回值),以及数组形式的ref或者reactive
3、watch监听的对象是reactive数据时,监听层级是深层次的
4、如果想要监听reactive对象属性值时,应该使用回调函数形式来监听【watch(()=> getValue, () => {})】
import{ref,watch}from'vue';constcount=ref(0);watch(count,(newValue,oldValue)=>{console.log(`Count changed from${oldValue}to${newValue}`);});functiongetValue(){returnstate.count/2}watch(getValue,(newV,oldV)=>{})watch([_count1,_count2,state,state2],(newV,oldV)=>{},{immediate:true,// 默认false 组件渲染时会执行一次deep:true,// 默认true 深度监听,但是如果监听对象是computed值或者Getter函数时默认就是falseonce:true// 默认false 监听器回调函数只执行一次flush:'post'// dom更新之后执行},)watchEffect
watchEffect 是一个基于响应式数据源的观察者,当响应式数据源变化时重新执行。
1、会立即执行一次
2、多依赖项监听可以使用watchEffect,而不是watch
3、如果只需要监听reactive对象的某个属性,那么watchEffect函数中只需要用到该属性即可,性能对比watch更优
import{ref,watchEffect}from'vue';constcount=ref(0);constunwatch=watchEffect(()=>{console.log(`Count is now:${count.value}`);});unwatch()// 停止侦听器