首頁  >  文章  >  web前端  >  詳解Vue3響應式的兩大利器:ref與reactive

詳解Vue3響應式的兩大利器:ref與reactive

青灯夜游
青灯夜游轉載
2023-01-09 18:32:552054瀏覽

相對於Vue2的defineProperty實現的資料響應式,Vue3對資料響應的處理分工更加明確,透過組合式api中ref與reactive兩個暴露給開發者的函數對資料進行包裝,從而實現了資料響應式,那麼它們有什麼差別?下面我們一起來根據例子來學習!

詳解Vue3響應式的兩大利器:ref與reactive

ref定義基本資料型態、引用資料型別的回應式。也就是說ref(value),這個value類型可以是基本資料類型,也可以是引用資料類型,但是在js中使用時必須以屬性.value格式使用,在template中可以直接調用數據。


reactive定義引用型別資料的回應式,不支援基本資料型別,如果需要寫基本資料型別只能是放在物件中,也是說reactive(value),這個value型別必須是參考型別。 【相關推薦:vuejs影片教學web前端開發


從上面兩個例子我們可以看出不管什麼類型數據,ref都需要以.value來呼叫ref定義的數據,對於引用資料型別來看,我們可以看出程式碼不美觀,所以一般對於引用型別數據,都建議使用reactive來定義對於基本資料類型,可以使用ref也可以使用reactive來定義。既然到了這裡我們都了解了ref和reactive的運用差異了,那麼我們繼續來一起探討它們的反應原理又有什麼差別呢?

揭秘ref

從上面的例子,我們先列印看一下基本資料型別(strRef)、引用資料型別(arrRef、ObjRef)的ref內部封裝結構是什麼樣的?如下三圖所示

詳解Vue3響應式的兩大利器:ref與reactive

詳解Vue3響應式的兩大利器:ref與reactive

詳解Vue3響應式的兩大利器:ref與reactive#從上面圖片可以看出,不管是什麼類型的數據,對於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就是為了區分引用型別資料是否做響應式處理。

詳解Vue3響應式的兩大利器:ref與reactive

  • #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,所以在调用的时候,需要带上。

詳解Vue3響應式的兩大利器:ref與reactive

其实,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);
}

詳解Vue3響應式的兩大利器:ref與reactive从上图可以看出,使用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属性给数据带上了五个属性:deletePropertygetsethasownKeys。这五个属性怎么来的?我们一起探讨一下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函数:

詳解Vue3響應式的兩大利器:ref與reactive

  • 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.ts
    export const mutableHandlers: ProxyHandler = {
      // 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.ts
    export const mutableCollectionHandlers: ProxyHandler = {
      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的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    陳述:
    本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除