preface

Version: 3.0.2

Vue3 response API- REF through the source code to elaborate

1. Responsive apis

ref

Function:

  • Implement data responsiveness of value types
  • template-boundDOMThe element

Usage:

html

<div id="app">
  <div>{{ count }}</div>
  <div ref="eleRef">ref</div>
  <button @click="add">add</button>
</div>
Copy the code

JavaScript

const { ref, createApp, onMounted } = Vue;
const app = createApp({
  setup() {
    const count = ref(1);
    
    // 1. If you want to change the value of count, you must set it to.value
    count.value++;
    
    const eleRef = ref(null);
    
    // Execute after the page is mounted
    onMounted(() = > {
      console.log(eleRef.value); // => <div>ref</div>
    });
    
    // 2. Bind the element on the page with the ref attribute to the ref object returned by setup
    return {
      count,
      eleRef
    }
  },
  methods: {
    add() {
      // 3. There is no. Value assignment
      this.count += 1;
    }
  }
}).mount("#app");
Copy the code

Two, source code analysis

Through the analysis of the source code, analyze the JS code above three important notes.

  • 1. To change the value of count, you must pass the. Value setting

Create the REF object.

function ref(value) {
  return createRef(value);
}

function createRef(rawValue, shallow = false) {
  if (isRef(rawValue)) {
    return rawValue;
  }
  return new RefImpl(rawValue, shallow);
}
Copy the code

Where isRef checks whether the value is a REF object.

function isRef(r) {
  return Boolean(r && r.__v_isRef === true);
}
Copy the code

RefImpl is the implementation class of the REF object. The class defines get value() and set value(newVal) for value and assignment, respectively, which explains the first comment.

class RefImpl {
  constructor(_rawValue, _shallow = false) {
    this._rawValue = _rawValue;
    this._shallow = _shallow;
    this.__v_isRef = true;
    // If it is shallow, return raw data, otherwise turn _value into a responsive object
    this._value = _shallow ? _rawValue : convert(_rawValue);
  }
  get value() {
    track(toRaw(this), "get" /* GET */.'value');
    return this._value;
  }
  set value(newVal) {
    if (hasChanged(toRaw(newVal), this._rawValue)) {
      this._rawValue = newVal;
      this._value = this._shallow ? newVal : convert(newVal);
      trigger(toRaw(this), "set" /* SET */.'value', newVal); }}}Copy the code

The convert method converts the parameters of an object type into a responsive object.

For track and trigger methods, I recommend reading my previous article on the responsive principle.

Portal: Vu3 responsive principle explained in detail

  • 2. Bind the elements on the page with ref attributes to the ref object returned by Setup

Process the return result of the setup function.

function handleSetupResult(instance, setupResult, isSSR) {
  // omit some source code...
  
  instance.setupState = proxyRefs(setupResult);
}
Copy the code

Add intercepts for REF data.

function unref(ref) {
  // if it is ref, the value of ref is returned
  return isRef(ref) ? ref.value : ref;
}
const shallowUnwrapHandlers = {
  // If the attribute is of type REF, the response is triggered to update the interface
  get: (target, key, receiver) = > unref(Reflect.get(target, key, receiver)),
  set: (target, key, value, receiver) = > {
    const oldValue = target[key];
    if(isRef(oldValue) && ! isRef(value)) { oldValue.value = value;return true;
    }
    else {
      return Reflect.set(target, key, value, receiver); }}};function proxyRefs(objectWithRefs) {
  return isReactive(objectWithRefs)
    ? objectWithRefs
  : new Proxy(objectWithRefs, shallowUnwrapHandlers);
}
Copy the code

Add a REF object for setupState. SetupState [ref] = value; The value of eleRef is set to the DOM where ref=”eleRef” has been added to the page.

const setRef = (rawRef, oldRawRef, parentComponent, parentSuspense, vnode) = > {
  // omit some source code...
  
  let value;
  // ...
  
  // value is the DOM element bound to ref
  value = vnode.el;
  
  // Add a ref object for setupState.
  const doSet = () = > {
    refs[ref] = value;
    if (hasOwn(setupState, ref)) {
      // bind DOM elements to ref
      // A value is set to trigger shallowUnwrapHandlers' SET interception, that is, the value attribute of the ref object is set to a new valuesetupState[ref] = value; }}; }Copy the code
  • Value cannot be assigned

Expose the setupState property to the Vue instance.

So in point 3, just use this.count += 1; Setupstate. count, which triggers the shallowUnwrapHandlers get method that returns the value of ref.

[setupState] instance.setupState[key]
function exposeSetupStateOnRenderContext(instance) {
  const { ctx, setupState } = instance;
  Object.keys(toRaw(setupState)).forEach(key= > {
    if (key[0= = ='$' || key[0= = ='_') {
      // Setup cannot return object properties that start with '$' or' _ '
      warn(`setup() return property The ${JSON.stringify(key)} should not start with "$" or "_" ` +
           `which are reserved prefixes for Vue internals.`);
      return;
    }
    Object.defineProperty(ctx, key, {
      enumerable: true.configurable: true.get: () = > setupState[key],
      set: NOOP
    });
  });
}
Copy the code

3 things to watch

1. Give the blogger a “like” if the post has helped you.

If you think the article is good, you can move your little hands and collect it.

3, if you want to see more source code details, you can add follow the blogger.

Appendix:

1, vue3. x complete version of the source code parsing: github.com/fanqiewa/vu…

2, other source code analysis: www.fanqiewa.xyz/