vue3响应式
vue3
实现响应式的方法有两种:
第一种运用组合式API中的
reactive
直接构建响应式,组合式API的出现让我们可以直接用
setup
函数来处理之前的大部分逻辑,同时也避免了
this
的使用,像
data
,
watch
,
computed
,生命周期函数都声明在
setup
函数中,这样就像
react-hooks
一样提升代码的复用率,逻辑性更强。
第二种就是使用传统的
data(){ return{} }
的形式,
vue3
并没有放弃对
vue2
写法的支持,而是对
vue2
的写法完全兼容。
响应式基础API
首先,让我们来看看
vue3
为响应式提供的一些基础
API
.
reactive
reactive
用于将数据变成响应式数据。调用
reactive
后返回的对象是响应式副本而非原始对象。其原理就是将传入的数据包装成一个
Proxy
对象。
import{ reactive, watchEffect }from'vue';const data ={count:0};const obj =reactive(data);
data === obj // falsewatchEffect(()=>{// 用于响应性追踪
console.log(obj.count);});setTimeout(()=>{
obj.count++;},2000);
响应式转换是“深层”的——它影响所有嵌套
property。在基于
ES2015 Proxy的实现中,返回的
proxy是不等于原始对象的。建议只使用响应式
proxy,避免依赖原始对象。
reactive
用于复杂数据类型,比如对象和数组等,当传入基础数据类型时,默认情况下修改数据,界面不会自动更新,如果想更新,可以通过重新赋值的方式。
readonly
接受一个对象 (响应式或纯对象) 或
ref
并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套
property
也是只读的,不能改变值,否则是报错。
import{ reactive, watchEffect, readonly }from'vue';const data ={count:0};const obj =reactive(data);const copy =readonly(obj);watchEffect(()=>{// 用于响应性追踪
console.log(obj.count)});setTimeout(()=>{
obj.count++;
copy.count++;},2000);
isProxy
检查对象是否是由
reactive
或
readonly
创建的
proxy
。
import{ reactive, readonly, isProxy }from'vue';const data ={count:0};const obj =reactive(data);const copy =readonly(obj);
console.log(isProxy(obj));// true
console.log(isProxy(copy));// true
console.log(isProxy(data));// false
isReactive
检查对象是否是由
reactive
创建的响应式代理。如果该代理是
readonly
创建的,但包裹了由
reactive
创建的另一个代理,它也会返回
true
。
import{ reactive, readonly, isReactive }from'vue';const data ={count:0};const obj =reactive(data);const copy =readonly(obj);const immute =readonly(data);
console.log(isReactive(obj));// true
console.log(isReactive(copy));// true
console.log(isReactive(data));// false
console.log(isReactive(immute))// false
isReadonly
检查对象是否是由
readonly
创建的只读代理。
import{ reactive, readonly, isReadonly }from'vue';const data ={count:0};const obj =reactive(data);const copy =readonly(obj);const immute =readonly(data);
console.log(isReadonly(obj));// false
console.log(isReadonly(copy));// true
console.log(isReadonly(data));// false
console.log(isReadonly(immute))// true
toRaw
返回
reactive
或
readonly
代理的原始对象。这是一个“逃生舱”,可用于临时读取数据而无需承担代理访问/跟踪的开销,也可用于写入数据而避免触发更改。不建议保留对原始对象的持久引用。请谨慎使用。
import{ reactive, readonly, toRaw }from'vue';const data ={count:0};const obj =reactive(data);const immute =readonly(data);
console.log(toRaw(obj)=== data);// true
console.log(toRaw(immute)=== data);// true
markRaw
标记一个对象,使其永远不会转换为
proxy
。返回对象本身。
import{ reactive, readonly, isReactive, markRaw }from'vue';const data =markRaw({count:0});const obj =reactive(data);const immute =readonly(data);
console.log(isReactive(obj));// false
console.log(obj === data);// true
console.log(immute === data);// true// 即使嵌套在其他响应式队中也是非响应式的const wrap =reactive({data});
console.log(isReactive(wrap.data));// false
但是如果对标记对象内的属性进行响应化,是可以的。
const count =reactive({// data被标记,data.count未被标记count: data.count
})
console.log(isReactive(count));
shallowReactive
创建一个响应式代理,它跟踪其自身
property
的响应性,但不执行嵌套对象的深层响应式转换。
import{ shallowReactive, isReactive, watchEffect }from'vue';const data ={count:0,nested:{count:0}};const obj =shallowReactive(data);watchEffect(()=>{// 追踪obj.count的变化
console.log(obj.count);});watchEffect(()=>{// 追踪obj.nested.count的变化
console.log(obj.nested.count);});
obj.count++;
obj.nested.count++;
console.log(data.count);// 0
console.log(data.nested.count);// 1
console.log(isReactive(obj.nested));// false
shallowReadonly
创建一个
proxy
,使其自身的
property
为只读,但不执行嵌套对象的深度只读转换。
import{ shallowReadonly, isReadonly }from'vue';const data ={count:0,nested:{count:0}};const obj =shallowReadonly(data);// 报错
obj.count++;
obj.nested.count++;
console.log(isReadonly(obj.nested));// false
ref
接受一个内部值并返回一个响应式且可变的
ref
对象。
ref
对象仅有一个
.value
值,指向该内部值。跟
reactive
类似,也是将数据变成响应式。
import{ ref, watchEffect }from'vue';const count =ref(0);const obj =ref({count:0})
console.log(obj)watchEffect(()=>{
console.log(count.value);})watchEffect(()=>{
console.log('obj.value.count: ', obj.value.count);})
count.value++;
obj.value.count++;
ref和reactive的区别
ref是把值类型添加一层包装,使其变成响应式的引用类型的值。ref(0) --> reactive( { value:0 })reactive则是引用类型的值变成响应式的值。
两者的区别只是在于是否需要添加一层引用包装,**对于对象而言,添加一层包装后会被
reactive
处理为深层的响应式对象,在调用
unref
后就能看到其实对象是一个
Reactive
对象**。
像上面的例子,使用
ref
同样可以将对象响应化,不过访问的时候需要调用
value.
去访问内部属性。所以对于对象而言,最好使用
reative
去响应化处理。
unref
如果参数是一个
ref
,则返回内部值,否则返回参数本身。这是
val = isRef(val) ? val.value : val
的语法糖函数。
import{ ref, unref }from'vue';const count =ref(0);const obj =ref({count:0})
console.log(unref(count));// 0
console.log(unref(obj))// Reactive对象
toRef
可以用来为源响应式对象上的某个属性新创建一个
ref
。然后,
ref
可以被传递,它会保持对其源属性的响应式连接。
import{ reactive, toRef }from'vue';const obj =reactive({count:0})const countRef =toRef(obj,'count');
countRef.value++;
console.log(obj.count)// 1
obj.count++;
console.log(countRef)// 2
toRefs
将响应式对象转换为普通对象,其中结果对象的每个属性都是指向原始对象相应属性的
ref
。
import{ reactive, toRefs }from'vue';const obj =reactive({count:0})const countRef =toRefs(obj);
countRef.value++;
console.log(obj.count)// 1
obj.count++;
console.log(countRef.count.value)// 2
toRefs
只会为源对象中包含的属性生成
ref
。如果要为特定的属性创建
ref
,则应当使用
toRef
。
isRef
检查值是否为一个
ref
对象。
import{ isRef, ref }from'vue';const count =ref(0);
console.log(isRef(count));// true
customRef
创建一个自定义的
ref
,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收
track
和
trigger
函数作为参数,并且应该返回一个带有
get
和
set
的对象。
使用自定义
ref
通过
v-model
实现
debounce
的示例:
<inputv-model="text"/>
functionuseDebouncedRef(value, delay =200){let timeout
returncustomRef((track, trigger)=>{return{get(){track()return value
},set(newValue){clearTimeout(timeout)
timeout =setTimeout(()=>{
value = newValue
trigger()}, delay)}}})}exportdefault{setup(){return{text:useDebouncedRef('hello')}}}
shallowRef
创建一个跟踪自身
.value
变化的
ref
,但不会使其值也变成响应式的。
const obj =shallowRef({})// 改变 ref 的值是响应式的
obj.value ={}// 但是这个值不会被转换。isReactive(obj.value)// false
triggerRef
手动执行与
shallowRef
关联的任何作用。
const shallow =shallowRef({greet:'Hello, world'})// 第一次运行时记录一次 "Hello, world"watchEffect(()=>{
console.log(shallow.value.greet)})// 这不会触发作用 (effect),因为 ref 是浅层的
shallow.value.greet ='Hello, universe'// 触发副作用 "Hello, universe"triggerRef(shallow)
computed
接受一个
getter
函数,并根据
getter
的返回值返回一个不可变的响应式
ref
对象。
const count =ref(1)const plusOne =computed(()=> count.value +1)
console.log(plusOne.value)// 2
plusOne.value++// 错误
或者,接受一个具有
get
和
set
函数的对象,用来创建可写的
ref
对象。
const count =ref(1)const plusOne =computed({get:()=> count.value +1,set:val=>{
count.value = val -1}})
plusOne.value =1
console.log(count.value)// 0
watch
vue3
中组合式
api
watch
与
vue2
中选项式的
watch
完全等效。
watch
需要监听特定的数据源,并在单独的回调函数中执行副作用。默认情况下,它也是惰性的——即回调仅在监听源发生变化时被调用。
与
watchEffect
相比,
watch
允许我们:
- 惰性地执行副作用;
- 更具体地说明应触发监听器重新运行的状态;
- 访问被监听状态的先前值和当前值。
监听单一源
源数据可以是一个
reactive
,也可以直接是一个
ref
语法
watch( name , callback, options ) ;
name: 需要监听的属性或者返回值的getter函数callback: 属性改变后执行的方法,接受两个参数newVal: 新值
oldVal: 旧值
options: 配置项,可配置如下deep:Boolean, 是否深度监听
immediate:Boolean,是否立即执行
// 侦听reactiveconst state =reactive({count:0,value:1})// 只监听对象中的countwatch(// 使用getter函数保证只监听了state中的count()=> state.count,(count, prevCount)=>{/* ... */})// 侦听state中所有属性watch(state,(newVal, oldVal)=>{// ...})// 直接侦听一个 refconst count =ref(0)watch(count,(count, prevCount)=>{/* ... */})
监听多个源
使用数组来同时侦听多个源数据,当任一源数据发生改变都会触发
watch
const state =reactive({count:0,value:1});const count =ref(0)watch([state, count],([newState, newCount],[oldState, oldCount])=>{
console.log(newState);
console.log(newCount);})
state.count++;
count.value++;
深度监听
在
watch
的第三个参数中传入
deep: true
const state =reactive({count:0,value:{status:false}});watch(()=> state,(newVal, oldVal)=>{
console.log('不会触发')})watch(()=> state,(newVal, oldVal)=>{
console.log('触发深度监听')},{deep:true})
state.value.status =true;
由于
getter
方法只返回
state
,对没有对内部的对象进行监听,因此内部对象的属性发生改变不会触发
watch
。
当没有使用
getter
方法而是传入
state
这个
reactive
数据,则不需要设置
deep: true
都会进行深度监听。
立即执行
在
watch
的第三个参数中传入
immediate: true
,当传入数据就会执行一次。
watchEffect
立即执行传入的一个函数,响应式追踪其依赖,在其依赖变更时重新运行该函数。
它会监听引用数据类型的所有属性,不需要具体到某个属性,一旦运行就会立即监听,组件卸载的时候会停止监听
const count =ref(0)watchEffect(()=> console.log(count.value))// -> logs 0setTimeout(()=>{
count.value++// -> logs 1},100)
停止监听
当
watchEffect
在组件的
setup()
函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
在一些情况下,也可以显式调用返回值以停止侦听
const stop =watchEffect(()=>{/* ... */})// laterstop()
清除副作用
有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (即完成之前状态已改变了) 。所以侦听副作用传入的函数可以接收一个
onInvalidate
函数作入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:
- 副作用即将重新执行时
- 侦听器被停止 (如果在
setup()或生命周期钩子函数中使用了watchEffect,则在组件卸载时)
watchEffect(onInvalidate=>{const token =performAsyncOperation(id.value)onInvalidate(()=>{// id has changed or watcher is stopped.// invalidate previously pending async operation
token.cancel()})})
版权归原作者 dralexsanderl 所有, 如有侵权,请联系我们删除。