ref:定义基本数据类型、引用数据类型的响应式。也就是说ref(value),这个value类型可以是基本数据类型,也可以是引用数据类型,但是在js中使用时必须以属性.value格式使用,在template中可以直接调用数据。
<template> <div> <div><button @click="changevalue">修改</button></div> <div> <p>当前strref:{{ strref }}</p> <p>当前objref:姓名:{{ objref.name }} 爱好:{{ objref.hobboy }}</p> <p>当前arrref:{{ arrref }}</p> </div> </div></template><script>import { definecomponent, ref, shallowref } from 'vue'export default definecomponent({ setup () { const strref = ref('sapper');// 基本数据类型 const arrref = ref([1, 3, 2]);// 数组类型 const objref = ref({ // 对象类型 name: 'sapper', hobboy: ['吉他', '原神'] }) const changevalue = () => { strref.value = '工兵'; arrref.value[1] = 4; objref.value.hobboy[1] = '滑冰'; } return {strref,objref,arrref,changevalue} }})</script>
reactive:定义引用类型数据的响应式,不支持基本数据类型,如果需要写基本数据类型只能是放在对象中,也就是说reactive(value),这个value类型必须是引用类型。【相关推荐:vuejs视频教程、web前端开发】
<template> <div> <div><button @click="changevalue">修改</button></div> <div> <div>当前objreactive: <br/> 姓名:{{ objreactive.name }}<br/> 爱好:{{ objreactive.hobboy }} </div> <div>当前arrreactive:{{ arrreactive }}</div> </div> </div></template><script>import { definecomponent, reactive } from 'vue'export default definecomponent({ setup () { const arrreactive = reactive([1, 3, 2]);// 数组类型 const objreactive = reactive({ // 对象类型 name: 'sapper', hobboy: ['吉他', '原神'] }) const changevalue = () => { arrreactive[1] = 4; objreactive.name = '工兵'; objreactive.hobboy[1] = '滑冰'; } return {objreactive,arrreactive,changevalue} }})</script>
从上面两个例子中我们可以看出不管什么类型数据,ref都需要以.value来调用ref定义的数据,对于引用数据类型来看,我们可以看出代码不美观,所以一般对于引用类型数据,都推荐使用reactive来定义;对于基本数据类型,可以使用ref也可以使用reactive来定义。既然到了这里我们都了解了ref和reactive的运用区别了,那么我们继续来一起探讨一下它们的响应原理又有什么区别?
揭秘ref从上面的例子,我们先打印看一下基本数据类型(strref)、引用数据类型(arrref、objref)的ref内部封装结构是什么样的?如下三图所示
从上面图片可以看出,不管是什么类型的数据,对于ref封装数据都是一个refimpl对象,reference implement的简写,是引用实现的意思,每个refimpl对象都有6个属性:
dep:是一个set类型的数据,用来存储当前的ref值收集的依赖。
_ v _ isref :标记位,只要被ref定义了,都会标识当前数据为一个ref,也就是它的值标记为true。
_ v _ isshallow:判断是否是shallowref定义的数据。
与ref不同的是,当使用shallowref为引用类型创建响应性时,修改深层属性,不具备响应性。只有对.value的引用时才触发。
const state = shallowref({ count: 1 })// 不会触发更改state.value.count = 2// 会触发更改state.value = { count: 2 }
_ rawvalue:用于保存当前ref值对应的原始值,如果传递的参数是对象,它就是用于保存转化前的原始值,否则_ value与_ rawvalue相同。
_ value:用于保存ref当前值,如果传递的参数是对象,它就是用于保存经过reactive函数转化后的值,否则_ value与_ rawvalue相同。从上面例子我们可以发现,对于引用类型的数据,它的值就是一个proxy对象,这其实就是reactive封装数据后对象(后面会说)。我们先看一下下图,发现_ rawvalue就是没有做响应性处理的原始值,在看看_ value是一个proxy对象就是做了reactive响应处理的值。
_ rawvalue与_ value就是为了区分引用类型数据是否做响应式处理。
value:保存的是当前的值。
既然我们清楚了ref给数据封装了什么属性,接下来开始探讨源码究竟给怎么给上面6个属性进行赋值的:
ref函数:vue3向开发者暴露的是ref函数,其实它就是封装了一个createref函数。
export function ref(value?: unknown) { return createref(value, false)}
createref函数:有两个参数,一个是要做响应处理的数据,一个是判断数据是否为shallowref定义的数据。它主要做的事情就是判断当前rawvalue(暂时没有做响应处理的数据)是否为ref类型数据、创建refimpl实例对象。
function createref(rawvalue: unknown, shallow: boolean) { if (isref(rawvalue)) { return rawvalue } return new refimpl(rawvalue, shallow)}
refimpl类:创建refimpl类给_ rawvalue和_ value属性赋值,判断当前定义的ref数据是否为shallowref定义的数据,然后获取响应性值时对数据依赖进行收集并返回_ value,修改响应式值时修改并通知依赖更新。
ref定义的数据为什么需要带.value调用数据? 就是因为refimpl类暴露给实例对象的get、set方法是value,所以在调用的时候,需要带上。
其实,refimpl实例关键就在于trackrefvalue(this)和triggerrefvalue(this, newval)的两个函数的处理,我们大概也知道它们就是依赖收集、依赖更新。这里就不一一探讨。
揭秘reactive上面也说了reactive封装数据的用法,它只支持传入引用类型数据(数组、对象),如果需要在reactive中使用基础类型只能放在对象中。既然这样我们来探讨一下reactive函数究竟做了什么?
const arrreactive = reactive([1, 3, 2]);// 数组类型const objreactive = reactive({ // 对象类型 name: 'sapper', hobboy: ['吉他', '原神']})const changevalue = () => { arrreactive[1] = 4; objreactive.name = '工兵'; objreactive.hobboy[1] = '滑冰'; console.log('arrreactive',arrreactive); console.log('objreactive',objreactive);}
从上图可以看出,使用reactive封装的数据返回的都是一个proxy对象,proxy就是代理,如把一个对象代理到另一个对象,好比如房子所有者,代理房子给二手房东销售,二手房东就可以拥有房子销售权利。从上图我们可以看到proxy对象有三个属性:
[[handler]]: 创建proxy对象传入的第二个参数,是对当前需要代理的目标target进行一些相关配置处理。
[[target]]:需要代理的目标target,也就是被代理的目标。
[[isrevoked]]:表示是否可撤销,生成可撤销的proxy对象用proxy.revocable()方法。那么proxy对象可以做什么?我们看看下面例子:
const houseowner = {home:'房源',price:1200,type:'一房一厅'};const proxyowner = new proxy(houseowner,{ get:function (target,key){ console.log(`${key}属性被访问!`) return target[key]; }, set:function(target,key,value){ if(target[key]===value){ return; } target[key] = value; return target[key]; },})console.log(proxyowner);proxyowner.price = 1300;// 对被代理对象的修改proxyowner.remark = '采光点好!';console.log(proxyowner.price);// price属性被访问
从这个例子,可以看出proxy对象的第二个参数给代理目标带上相关属性:set方法、get方法,再回到reactive封装的数据中,数据的handler属性给数据带上了五个属性:deleteproperty、get、set、has、ownkeys。这五个属性怎么来的?我们一起探讨一下vue3的源码实现:
// 源码位置:core-main/packages/reactivity/src/reactive.ts// vue3中暴露给开发者的是reactive方法export function reactive(target: object) { // 判断target是否只读,是就不做处理 if (isreadonly(target)) { return target } return createreactiveobject( target, false, mutablehandlers, mutablecollectionhandlers, reactivemap )}
createreactiveobject函数主要为了创建proxy实例对象,参数传了五个属性: target(目标数据)、isreadonly(target是否只读)、mutablehandlers(proxyhandler)、mutablecollectionhandlers(proxyhandler类型)、proxymap(数据集合)。我们先了解一波createreactiveobject函数:
reactiveflags:响应式数据标记。
export const enum reactiveflags { skip = '__v_skip',// 标记对象不可进行代理 is_reactive = '__v_isreactive',// 是否是reactive封装的 is_readonly = '__v_isreadonly',// 是否只读 is_shallow = '__v_isshallow',// 是否是shallowref封装的 raw = '__v_raw'// 是否是proxy原始的target}
targettype:target的数据类型。
const enum targettype { invalid = 0, common = 1,// array、object类型 collection = 2 // set、map、waekmap、weakset类型}
basehandlers:对于array、object类型数据,proxy实例的第二个参数。传入的basehandlers就是mutablehandlers。这个函数主要是为了给proxy对象带上五个属性。
// 源码位置:core-main/packages/reactivity/src/basehandlers.tsexport const mutablehandlers: proxyhandler<object> = { // creategetter() 主要实现依赖收集和reflect.set(target, key, value, receiver) get, // createsetter() 主要实现通知依赖更新和reflect.get(target, key, receiver) set, // deleteproperty() 主要是删除target的指定key的属性reflect.deleteproperty(target, key) deleteproperty, // has() 主要是判断target是否存在指定key的属性,reflect.has(target, key) has, // ownkeys() 主要是获取target的key数组,reflect.ownkeys(target) ownkeys}
collectionhandlers:对于set、map、waekmap、weakset类型数据,proxy实例的第二个参数。传入的basehandlers就是mutablecollectionhandlers。mutablecollectionhandlers主要是对 set、map、weakset、weakmap 四种类型的对象进行劫持。
// 源码位置:core-main/packages/reactivity/src/mutablecollectionhandlers.tsexport const mutablecollectionhandlers: proxyhandler<collectiontypes> = { get: /*#__pure__*/ createinstrumentationgetter(false, false)}function createinstrumentationgetter(isreadonly: boolean, shallow: boolean) { const instrumentations = shallow? isreadonly? shallowreadonlyinstrumentations: shallowinstrumentations : isreadonly? readonlyinstrumentations: mutableinstrumentations return (target: collectiontypes,key: string | symbol,receiver: collectiontypes) => { ... return reflect.get( hasown(instrumentations, key) && key in target? instrumentations:target, key, receiver ) }}
总结:
ref:定义基本数据类型、引用数据类型的响应式。封装数据类型为ref类型,主要就是创建了refimpl实例对象。reactive:定义引用类型数据的响应式,不支持基本数据类型,如果需要写基本数据类型只能是放在对象中。封装数据为reactive类型,主要是创建了proxy实例对象,通过reflect实现数据的获取与修改。(学习视频分享:vuejs入门教程、编程基础视频)
以上就是详解vue3响应式的两大利器:ref与reactive的详细内容。
