SetupStatefulComponent: setupStatefulComponent: setupStatefulComponent: setupStatefulComponent: setupStatefulComponent: setupStatefulComponent: setupStatefulComponent: setupStatefulComponent: setupStatefulComponent: setupStatefulComponent: setupStatefulComponent

In this case

const { ref, createApp } = Vue
const app = createApp({ 
    // Vue3 added setup property
    setup(props) { 
        const message = ref('Test data')
        const modifyMessage = () = > {
            message.value = 'Modified test data'
        }
        return { message, modifyMessage }
    } 
}).mount('#app')
Copy the code

Setup method execution

// Define the callWithErrorHandling function
export function callWithErrorHandling(
  fn: Function,
  instance: ComponentInternalInstance | null, type: ErrorTypes, args? : unknown[]) {
  let res
  try{ res = args ? fn(... args) : fn() }catch (err) {
    handleError(err, instance, type)
  }
  return res
}

The setupStatefulComponent method resolves the setup location
const setupResult = callWithErrorHandling(
  setup,
  instance,
  ErrorCodes.SETUP_FUNCTION,
  [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
Copy the code

You can see that resolving setup executes the setup method in callWithErrorHandling with [instance.props, setupContext]. Instance. props(empty object) is generated when the instance object is created, and setupContext is interpreted based on the length of setup, which in this case is null. Let’s start by executing the setup function.

const message = ref('Test data')
const modifyMessage = () = > {
    message.value = 'Modified test data'
} 
return { message, modifyMessage }
Copy the code

Ref function internal implementation (create RefImpl class instance object)

The setup method is implemented internally (as interpreted in this example) by first defining the data message, which executes the ref method, one of the functions exposed by Vue3

// ref
export function ref(value? : unknown) {
  return createRef(value)
}
// isRef
export function isRef(r: any) :r is Ref {
  return Boolean(r && r.__v_isRef === true)}// createRef
function createRef(rawValue: unknown, shallow = false) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}
Copy the code

The createRef function is called internally to determine whether the data contains the __v_isRef attribute. If it does, the data is returned directly. Otherwise, new RefImpl() creates an instance.

/ / RefImpl class
class RefImpl<T> { private _value: T private _rawValue: T public dep? : Dep =undefined
  public readonly __v_isRef = true

  constructor(value: T, public readonly _shallow = false) {
    this._rawValue = _shallow ? value : toRaw(value)
    this._value = _shallow ? value : convert(value)
  }

  get value() {
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    newVal = this._shallow ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = this._shallow ? newVal : convert(newVal)
      triggerRefValue(this, newVal)
    }
  }
}

// ReactiveFlags.RAW = __v_raw
export function toRaw<T> (observed: T) :T {
  const raw = observed && (observed as Target)[ReactiveFlags.RAW]
  return raw ? toRaw(raw) : observed
}

const convert = <T extends unknown>(val: T): T =>
  isObject(val) ? reactive(val) : val
Copy the code

The RefImpl class defines the __v_isRef attribute as true and the DEp attribute as undefined, then performs constructor to pass in value(in this case “test data “) and sets the _rawValue attribute to toRaw(value).

The toRaw method checks whether the data contains a __v_RAW attribute. If it does, toRaw(value’s __v_RAW attribute value) is recursively called, otherwise value is returned

Set _value to convert(value)

The convert method is used to check whether the data is of a reference type. If the data is of a reference type, the reactive method will be called to set the internal hook function. In this example, the reactive method will not be pared for the basic data type.

RefImpl class internal hook function (dependency collection, run dependency)

The RefImpl class finally sets the get and set hook functions for value. The get function triggers the collection of dependencies when obtaining the value property of the instance object, and returns the _value property of the instance, which is the data passed in when creating the instance object. The set hook replaces the new value and runs the dependency.

How to trigger the value hook and how to collect the dependencies will be analyzed in detail when parsing the generated render method

Back to the setup method itself, now that the data is processed, let’s look at the methods

const modifyMessage = () = > { message.value = 'Modified test data' }
Copy the code

If we look at the body of the function, we can see that the value property of the data is set to a new value, so the set hook function should be triggered here.

As for how the method of modifying data is attached to the button click event, we will wait until the template is parsed and the render function is processed

Finally, the setup function returns an object {message, modifyMessage}. At this point, the setup method is complete, and the object returned is the one returned by the callWithErrorHandling method. We then revert to the setupStatefulComponent method

Proxy Data object returned by Proxy Setup

if (isPromise(setupResult)) {
    // ...
} else {
  handleSetupResult(instance, setupResult, isSSR)
}
Copy the code

The setupResult returned in this case is not a Promise object, so the handleSetupResult method is executed

export function handleSetupResult(instance: ComponentInternalInstance, setupResult: unknown, isSSR: boolean) {
    if (isFunction(setupResult)) {}
    else if (isObject(setupResult)) {
        // ...
        instance.setupState = proxyRefs(setupResult)
        if (__DEV__) {
          exposeSetupStateOnRenderContext(instance)
        }
        // ...
    } else if(__DEV__ && setupResult ! = =undefined) {}
    finishComponentSetup(instance, isSSR)
}

// proxyRefs
export function proxyRefs<T extends object> (
  objectWithRefs: T
) :ShallowUnwrapRef<T> {
  return isReactive(objectWithRefs)
    ? objectWithRefs
    : new Proxy(objectWithRefs, shallowUnwrapHandlers)
}

// 
const shallowUnwrapHandlers: ProxyHandler<any> = {
  get: (target, key, receiver) = > unref(Reflect.get(target, key, receiver)),
  set: (target, key, value, receiver) = > {
    // ...}}Copy the code

As you can see inside the handleSetupResult method, proxyRefs is first executed to set the set and GET hook functions for its properties and assign them to instance.setupState using the result of the Proxy setup function. Then perform exposeSetupStateOnRenderContext method.

Instance. CTX Added Proxy object properties

export function exposeSetupStateOnRenderContext(
  instance: ComponentInternalInstance
) {
  const { ctx, setupState } = instance
  Object.keys(toRaw(setupState)).forEach(key= > {
    if(! setupState.__isScriptSetup && (key[0= = ='$' || key[0= = ='_')) {
      // ... console.warn()
      return
    }
    Object.defineProperty(ctx, key, {
      enumerable: true.configurable: true.get: () = > setupState[key], // ctx.xxx = setupState['xxx']
      set: NOOP
    })
  })
}
Copy the code

This function assigns attributes from setupState to CTX via Object.defineProperty

Setupstate. message: setupState.message: setupState.message: setupState.message: setupState.message: setupState.message: setupState.message: setupState.message: setupState.message The unref method of the GET hook function is used to retrieve the value property of the RefImpl instance, which triggers the GET hook function of the RefImpl instance to retrieve data and collect dependencies

At the end of the handleSetupResult method, the finishComponentSetup function is executed. After completing the setup parsing, the template template is compiled.

conclusion

The setup function, which is internally defined using ref(or reactive, as exposed by Vue3), essentially creates RefImpl instantiations and adds get and SET hook functions. The setup function returns an object that is proxyed and assigned to instance’s setupState property (in preparation for the hook function to fire when the render function encounters the property name). Finally, assign the property value of the setupState Object via object.defineProperty to the CTX Object (used when parsing the Render function). The template template will be parsed to see how the render function is generated.