Home >Web Front-end >Vue.js >Detailed explanation of the two major responsive tools of Vue3: ref and reactive

Detailed explanation of the two major responsive tools of Vue3: ref and reactive

青灯夜游
青灯夜游forward
2023-01-09 18:32:552317browse

Compared with the data responsiveness implemented by Vue2's defineProperty, Vue3 has a clearer division of labor in processing data responses. The data is packaged through the two functions of ref and reactive exposed to developers in the combined API, thereby realizing data response. formula, so what’s the difference between them? Let’s learn together with examples!

Detailed explanation of the two major responsive tools of Vue3: ref and reactive

ref: Defines the responsive of basic data types and reference data types. In other words, ref(value), this value type can be a basic data type or a reference data type, but when used in js, it must be used in the format of property.value, and can be called directly in the template data.

<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 &#39;vue&#39;
export default defineComponent({
  setup () {
    const strRef = ref(&#39;sapper&#39;);// 基本数据类型
    const arrRef = ref([1, 3, 2]);// 数组类型
    const objRef = ref({  // 对象类型
      name: &#39;sapper&#39;,
      hobboy: [&#39;吉他&#39;, &#39;原神&#39;]
    })
    const changeValue = () => {
      strRef.value = &#39;工兵&#39;;
      arrRef.value[1] = 4;
      objRef.value.hobboy[1] = &#39;滑冰&#39;;
    }
    return {strRef,objRef,arrRef,changeValue}
  }
})
</script>

reactiveDefine the reactive type of reference type data, which does not support basic data types. If you need to write basic data types, you can only put them in objects, or That is to say, reactive(value), this value type must be a reference type. [Related recommendations: vuejs video tutorial, web front-end development]

<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 &#39;vue&#39;
export default defineComponent({
  setup () {
    const arrReactive = reactive([1, 3, 2]);// 数组类型
    const objReactive = reactive({  // 对象类型
      name: &#39;sapper&#39;,
      hobboy: [&#39;吉他&#39;, &#39;原神&#39;]
    })
    const changeValue = () => {
      arrReactive[1] = 4;
      objReactive.name = &#39;工兵&#39;;
      objReactive.hobboy[1] = &#39;滑冰&#39;;
    }
    return {objReactive,arrReactive,changeValue}
  }
})
</script>

From the above two examples we can see that no matter what type of data, ref needs to be .value to call the data defined by ref. For reference data types, we can see that the code is not beautiful, so generally it is recommended to use reactive to define for reference type data; For basic data types, you can use ref or reactive to define . Now that we all understand the difference between the use of ref and reactive here, let's continue to explore the differences in their response principles?

Revealing ref

From the above example, we first print and look at the ref internal encapsulation structure of the basic data type (strRef) and reference data type (arrRef, ObjRef) what does it look like? As shown in the following three pictures

Detailed explanation of the two major responsive tools of Vue3: ref and reactive

Detailed explanation of the two major responsive tools of Vue3: ref and reactive

Detailed explanation of the two major responsive tools of Vue3: ref and reactiveAs can be seen from the above pictures, no matter what type of data it is, for ref encapsulated data They are all a RefImpl object, the abbreviation of reference implement, which means reference implementation. Each RefImpl object has 6 attributes:

  • dep: It is a Set type data, used to store the dependencies collected by the current ref value.

  • _ v _ isRef: Mark bit, as long as it is defined by ref, it will mark the current data as a Ref, that is, its value is marked as true.

  • _ v _ isShallow: Determine whether it is the data defined by shallowRef.

    Different from ref, when shallowRef is used to create responsiveness for a reference type, deep attributes are modified and there is no responsiveness. Fired only for references to .value.

    const state = shallowRef({ count: 1 })
    // 不会触发更改
    state.value.count = 2
    // 会触发更改
    state.value = { count: 2 }
  • _ rawValue: used to save the original value corresponding to the current ref value, if the passed parameter is Object , which is used to save the original value before conversion, otherwise _ value is the same as _ rawValue.

  • _ value: used to save the current value of ref, if the passed parameter is an object, it isUsed to save the value converted by the reactive function , otherwise _ value is the same as _ rawValue. From the above example, we can find that for reference type data, its value is a proxy object, which is actually the reactive object after encapsulating the data (discussed later). Let's first look at the picture below and find that _ rawValue is the original value without responsive processing. Then we look at _ value which is a proxy object that has been processed with reactive response.

    _ rawValue and _ value are to distinguish whether reference type data should be processed responsively.

Detailed explanation of the two major responsive tools of Vue3: ref and reactive

  • value: Saves the current value.

Now that we know what attributes ref encapsulates for data, let’s start exploring how the source code assigns values ​​to the above six attributes:

  • ref function: Vue3 exposes the ref function to developers. In fact, it encapsulates a createRef function.

    export function ref(value?: unknown) {
      return createRef(value, false)
    }
  • createRef function: There are two parameters, one is the data to be processed for response , and the other is to determine whether the data is Data defined for shallowRef. What it mainly does is to determine whether the current rawValue (data that has not yet been processed for response) is ref type data and create a RefImpl instance object.

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

Detailed explanation of the two major responsive tools of Vue3: ref and reactive

其实,RefImpl实例关键就在于trackRefValue(this)triggerRefValue(this, newVal)的两个函数的处理,我们大概也知道它们就是依赖收集、依赖更新。这里就不一一探讨。

揭秘Reactive

上面也说了reactive封装数据的用法,它只支持传入引用类型数据(数组、对象),如果需要在reactive中使用基础类型只能放在对象中。既然这样我们来探讨一下reactive函数究竟做了什么?

const arrReactive = reactive([1, 3, 2]);// 数组类型
const objReactive = reactive({  // 对象类型
  name: &#39;sapper&#39;,
  hobboy: [&#39;吉他&#39;, &#39;原神&#39;]
})
const changeValue = () => {
  arrReactive[1] = 4;
  objReactive.name = &#39;工兵&#39;;
  objReactive.hobboy[1] = &#39;滑冰&#39;;
  console.log(&#39;arrReactive&#39;,arrReactive);
  console.log(&#39;objReactive&#39;,objReactive);
}

Detailed explanation of the two major responsive tools of Vue3: ref and reactive从上图可以看出,使用reactive封装的数据返回的都是一个proxy对象,proxy就是代理,如把一个对象代理到另一个对象,好比如房子所有者,代理房子给二手房东销售,二手房东就可以拥有房子销售权利。从上图我们可以看到Proxy对象有三个属性:

  • [[Handler]]: 创建Proxy对象传入的第二个参数,是对当前需要代理的目标target进行一些相关配置处理。

  • [[Target]]:需要代理的目标target,也就是被代理的目标。

  • [[IsRevoked]]:表示是否可撤销,生成可撤销的proxy对象用Proxy.revocable()方法。 那么Proxy对象可以做什么?我们看看下面例子:

    const houseOwner = {home:&#39;房源&#39;,price:1200,type:&#39;一房一厅&#39;};
    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 = &#39;采光点好!&#39;;
    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函数:

Detailed explanation of the two major responsive tools of Vue3: ref and reactive

  • ReactiveFlags:响应式数据标记。

    export const enum ReactiveFlags {
      SKIP = &#39;__v_skip&#39;,// 标记对象不可进行代理
      IS_REACTIVE = &#39;__v_isReactive&#39;,// 是否是Reactive封装的
      IS_READONLY = &#39;__v_isReadonly&#39;,// 是否只读
      IS_SHALLOW = &#39;__v_isShallow&#39;,// 是否是shallowRef封装的
      RAW = &#39;__v_raw&#39;// 是否是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<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.ts
    export 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入门教程编程基础视频

The above is the detailed content of Detailed explanation of the two major responsive tools of Vue3: ref and reactive. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.cn. If there is any infringement, please contact admin@php.cn delete