preface

We all know that Vue3.0 has an update to all the underlying code, especially reactive and 2.0.

Object.defineproperty() is used for data hijacking in 2.0, but object.defineProperty is used for data hijacking of all attributes, not target objects, and arrays cannot be hijacked. In the original array method on the transformation of realization;

Proxy has an advantage that it can monitor the whole Object and detect the changes of data without separately monitoring the properties of a single Object, which reduces the cost of performance compared with the previous single property monitoring. Moreover, it can monitor arrays. The argument is an array but returns an object;

Here’s an example:

import { reactive } from 'vue';

const arr = reactive([1.2.3.4]);

console.log(arr); {0: 1, 1: 2, 2: 3, 3: 4}

Copy the code

Ref principle and usage

Ref takes a parameter value and returns a responsive, mutable ref object. The ref object has a single.value attribute that points to an internal value. So how does that work

// Can be a property of an object
export function ref<T extends object> (
  value: T
) :T extends Ref ? T : Ref<UnwrapRef<T>> // Can also be an arbitrary valueexport function ref<T> (value: T) :Ref<UnwrapRef<T>>

export function ref<T = any> () :Ref<T | undefined>
// refThe value of is optional and not mandatoryexport function ref(value? : unknown) {
  return createRef(value)}Copy the code

The ref argument can be any value, and the value returned is either the current argument or undefined. And if you pass nothing, it’s fine, it will return a response object but value is undefined;

Here’s an example:

import { ref } from 'vue';
const refParam = ref();

console.log(refParam) RefImpl {_rawValue: undefined, _shallow: false, __v_isRef: true, _value: undefined}

Copy the code

So let’s move on

// If it is an object, use reactive as the depth proxy, otherwise return it directly
const convert = <T extends unknown>(val: T): T =>
  isObject(val) ? reactive(val) : val
Copy the code
Shallow is used to indicate whether the proxy is shallow or deep
function createRef(rawValue: unknown, shallow = false) {
  if (isRef(rawValue)) {
    return rawValue
  }
  let value = shallow ? rawValue : convert(rawValue) // Convert if it is an object, use reactive as the depth proxy, otherwise return
  const r = {
    __v_isRef: true.get value() {
      // Method trace
      track(r, TrackOpTypes.GET, 'value')
      return value
    },
    set value(newVal) {
      // Check whether the new value and the old value are consistent, if not, update the operation
      if (hasChanged(toRaw(newVal), rawValue)) {
        rawValue = newVal
        value = shallow ? newVal : convert(newVal)
        trigger(r, TriggerOpTypes.SET, 'value', newVal)
      }
    }
  }
  return r
}
Copy the code

From the above code we can analyze it, we pass in the parameters will be judged parsing; 1. Look at the first judgment, if it is proxied, no more operations will be done, and the value will be returned directly. Shallow indicates shallow proxy value (shallow indicates shallow proxy value); If the depth broker executes the convert method, it will check if it is an object. If it is an object, it will check if it is an object. If it is an object, it will check if it is an object, and if it is an object, it will check if it is an object. 4. As can be seen from the return value, any object, array or other arbitrary value will be returned as an object during ref processing

Computed computer properties 3.0 implementation source code


// The value returned is read-only and reactive
export function computed<T> (getter: ComputedGetter<T>) :ComputedRef<T> // Added read and write operations for computer attributesexport function computed<T> (
  options: WritableComputedOptions<T>
) :WritableComputedRef<T> // The argument can be an object or a methodexport function computed<T> (
  // The argument is a method or an object argument. If the method is read-only, and if the object is read-write, the argument can be read or written
  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
) {
  let getter: ComputedGetter<T>
  let setter: ComputedSetter<T< span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 14px! Important; word-break: inherit! Important;if (isFunction(getterOrOptions)) {
    getter = getterOrOptions
    setter = __DEV__
      ? () = >{
          console.warn('Write operation failed: computed value is readonly')
        }
      : NOOP
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }

  let dirty = true
  let value: T
  let computed: ComputedRef<T>

  // effect passes in a getter. This getter = if the current argument is a method, the value is directly assigned to the current function, otherwise it is assigned to the object's get method
  const runner = effect(getter, {
    lazy: true./ / lazy to perform
    scheduler: () = > {
      if(! dirty) { dirty =true
        trigger(computed, TriggerOpTypes.SET, 'value')
      }
    }
  })
  computed = {
    // This is marked as a response that requires a proxy
    __v_isRef: true.// Is a read-only flag[ReactiveFlags.IS_READONLY]: isFunction(getterOrOptions) || ! getterOrOptions.set,// expose effect so computed can be stopped
    effect: runner,
    get value() {
      if (dirty) {
        value = runner()
        dirty = false
      }
      track(computed, TrackOpTypes.GET, 'value')
      return value
    },
    set value(newValue: T) {
      setter(newValue)
    }
  } as any
  return computed
}

Copy the code

Actually can see from the above code, the computed support read and write operations, if we are in the use of write operations under this case, for example, when we give the computed transfer is a method of default is read-only mode, can not be modified in the operation, but if we want to modify this value if you need to use the object in the form of parameters, Note {the get () = > {}, the set () = > {}} to get and set is tie-in use, otherwise it will throw an error; Look at this example:

// Read-only mode operation
const count = ref(1)
const plusOne = computed(() = > count.value + 1)

console.log(plusOne.value) / / 2

plusOne.value++ / / error!
Copy the code
// Read-write mode operation
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
Copy the code

reactive

A reactive proxy that takes an ordinary object and returns that ordinary object. A reactive transformation is “deep” : it affects all nested properties inside the object. The Proxy implementation based on ES2015 does not return a Proxy object equal to the original object. It is recommended to use only proxy objects instead of relying on primitive objects. Below look directly in the source code how to write, and then we analyze

// only unwrap nested ref
/ / the nested
type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>

export function reactive<T extends object> (target: T) :UnwrapNestedRefs<T>
export function reactive(target: object) {
  // if trying to observe a readonly proxy.return the readonly version// If the current target object exists and is read-only, the current object is returnedif (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  // Create a responsive object
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers
  )
}
Copy the code

Reactive can also receive an array. If the target object exists and is read-only, the object will be returned. Otherwise, a Reactive object will be created. So what happens to the creation process? And then we go down

function createReactiveObject(
  target: Target,                         // Target object to be proxied
  isReadonly: boolean,                    // Is a read-only object
  baseHandlers: ProxyHandler<any>,        // Basic processing method
  collectionHandlers: ProxyHandler<any>   // Collect dependencies
) {
  if(! isObject(target)) {if (__DEV__) {
      console.warn(`value cannot be made reactive: The ${String(target)}`)}return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  // The current object is not the original object, and is proxied read-only object
  if( target[ReactiveFlags.RAW] && ! (isReadonly && target[ReactiveFlags.IS_REACTIVE]) ) {return target
  }
  // target already has corresponding Proxy
  // Return the proxy object if it already exists
  const reactiveFlag = isReadonly
    ? ReactiveFlags.READONLY
    : ReactiveFlags.REACTIVE
  if (hasOwn(target, reactiveFlag)) {
    return target[reactiveFlag]
  }
  // only a whitelist of value types can be observed.

  if(! canObserve(target)) {return target
  }
  const observed = new Proxy(
    target,
    collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers
  )
  def(target, reactiveFlag, observed)
  return observed
}
Copy the code

Reactive receives an object form. If it is not an object form, it will throw an error. You may wonder why arrays can also be used.

export constisObject = (val: unknown): val is Record<any, any> => val ! = =null && typeof val === 'object'
Copy the code

Through the above analysis, it is not difficult to see that the differences between REF and Reactive and computed are quite large.

Reactive can only receive a reference object. Reactive cannot transmit common types of data, such as strings and numbers, but REF can, because ref returns a reactive proxy object whose value is our transfer parameter. If you want to delegate an object or an array, reactive is more appropriate. If you want to delegate a value of a common type, ref is more appropriate.

Computed to receive a responsive object or value, and to perform operations on the received object, is not responsive information itself, but tracing, obtaining, and modifying the responsive parameters;

This is the end of today’s sharing, if there is something wrong, please feel free to post it in the comments section below, we can discuss together; If you want to experience VUe3.0, you can also refer to my article vue3 to learn how to use vue3