In the last few articles we’ve looked at Vue3’s new Composition API, but today I’m going to take a look at Vue3’s new Composition API — Setup.

In most cases, components were stateful components that were labeled stateful Comonents during initialization. When Vue3 detected that we were dealing with stateful comonents, the function setupStatefulComponent was called. To initialize a stateful component. The source code for handling components is at @vue/ run-time core/ SRC /component.ts.

setupStatefulComponent

Let me take you through the setupStatefulComponent process:

function setupStatefulComponent(
  instance: ComponentInternalInstance,
  isSSR: boolean
) {
  const Component = instance.type as ComponentOptions

  if (__DEV__) { /* Detect component names, directives, compile options, and so on, and report errors */ }
    
  // 0. Create an access cache for the attributes of the render proxy
  instance.accessCache = Object.create(null)
  // 1. Create a common example or renderer proxy
  // It will be marked raw, so it will not be traced
  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)

  // setup()
  const { setup } = Component
  if (setup) {
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null)

    currentInstance = instance
    pauseTracking()
    const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
    )
    resetTracking()
    currentInstance = null

    if (isPromise(setupResult)) {
      if (isSSR) {
        // Returns a promise, so the server render can wait for it to execute.
        return setupResult
          .then((resolvedResult: unknown) = > {
            handleSetupResult(instance, resolvedResult, isSSR)
          })
          .catch(e= > {
            handleError(e, instance, ErrorCodes.SETUP_FUNCTION)
          })
      }
    } else {
      // Capture the Setup execution result
      handleSetupResult(instance, setupResult, isSSR)
    }
  } else {
    // Complete component initialization
    finishComponentSetup(instance, isSSR)
  }
}
Copy the code

The Component starts by initializing a Component variable that holds the Component’s options. Then if it is the DEV environment, it starts to detect the naming of various options in the components such as Name, Components, Directives, etc., and will report a warning in the development environment if there is a problem with the tests.

After the detection is complete, the proper initialization process begins, starting with the creation of an accessCache property on the instance that caches the renderer agent properties to reduce the number of reads. A proxy property is then initialized on the component instance that proxies the context of the component and is set to watch the raw value so that the proxy object will not be traced.

It’s time to tackle the setup logic we’re interested in in this article. First remove the setup function from the component, here to determine whether there is a setup function, if not, then directly jump to the bottom of the logic, execute finishComponentSetup, complete the component initialization. Otherwise it will enter the branch condition after if (setup).

CreateSetupContext generates a context object for setup depending on whether the number of parameters in the setup function is greater than 1.

A call to length on the function object returns the number of parameters to the function.

Here’s an example:

setup() // setup.length === 0

setup(props) // setup.length === 1

setup(props, { emit, attrs }) // setup.length === 2
Copy the code

By default, props is the required parameter when invoking setup, so the condition for generating a context for setup is setup.length > 1.

So following the code logic, let’s take a look at what’s really going on in the setup context.

export function createSetupContext(
  instance: ComponentInternalInstance
) :SetupContext {
  const expose: SetupContext['expose'] = exposed= > {
    instance.exposed = proxyRefs(exposed)
  }

  if (__DEV__) {
    /* DEV logic ignores, setting getter */ for context options
  } else {
    return {
      attrs: instance.attrs,
      slots: instance.slots,
      emit: instance.emit,
      expose
    }
  }
}
Copy the code

Expose the use of

Looking at the logic of the createSetupContext function, we see that the Setup context has the three familiar attributes attrs, slots, and emit as described in the documentation, and we were surprised to see an expose attribute returned that was not documented.

Expose was a proposal in an earlier Vue RFC, and the idea for Expose was to provide a platform like Expose ({… PublicMembers}), so component authors can use the API in Setup () to explicitly control what is explicitly exposed to component consumers.

When you’re encapsulating a component, use expose to limit output if too much is exposed in the REF. Of course, this is only an RFC proposal, interested partners can secretly taste oh.

import { ref } from 'vue'
export default {
  setup(_, { expose }) {
    const count = ref(0)

    function increment() {
      count.value++
    }
    
    // Expose only increment to the parent component
    expose({
      increment
    })

    return { increment, count }
  }
}
Copy the code

For example, when you use Expose as in the code above, the parent component gets only the INCREMENT property in the Ref object, and the count property is not exposed.

Executing the setup function

After processing the context of the setupContext, the component stops relying on the collection and starts executing the setup function.

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

Vue will call setup via callWithErrorHandling. Here we can see the last line, which is passed as the args argument. As described above, props will always be passed, If setup.length <= 1, setupContext is null.

After setup is called, the dependency collection state is reset. Next determine the return value type of setupResult.

If the setup function returns a value of type Promise and is rendered by the server, it waits for execution to continue. Otherwise, an error is reported saying that the current version of Vue does not support setup returning promise objects.

If a value is not returned by a Promise type, the result is processed by the handleSetupResult function.

export function handleSetupResult(
  instance: ComponentInternalInstance,
  setupResult: unknown,
  isSSR: boolean
) {
  if (isFunction(setupResult)) {
    Setup returns an inline rendering function
    if (__NODE_JS__ && (instance.type as ComponentOptions).__ssrInlineRender) {
      // When the name of this function is ssrRender (compiled via SFC inline mode)
      // render the function as a server-side function
      instance.ssrRender = setupResult
    } else {
      // Otherwise use the function as a render function
      instance.render = setupResult as InternalRenderFunction
    }
  } else if (isObject(setupResult)) {
    // Convert the returned object to a responsive object and set it to the instance's setupState property
    instance.setupState = proxyRefs(setupResult)
  }
  finishComponentSetup(instance, isSSR)
}
Copy the code

In the handleSetupResult result capture function, the type of result returned by setup is first determined as the ssrRender property if it is a function and the server’s in-line rendering function. In the case of non-server rendering, it is handled directly as the render function.

It then determines if the setup result is an object, converts that object to a proxy object and sets it to the component instance’s setupState property.

The component is eventually created by calling finishComponentSetup just like any other component that doesn’t have a Setup function.

finishComponentSetup

The main function of this function is to get and set the render function for the component. There are three canonical behaviors for templates and how to get render functions:

1. The render function may already exist, with setup returning the result. For example, setup, which we discussed in the previous section, returns a function.

2. If setup does not return, try to get the Component template and compile it, getting the render function from component.render,

3. If this function still has no render function, set instance.render to null so that it can get render functions from mixins/extend, etc.

Under the guidance of this canonical behavior, this first determines the server render situation, then determines the situation where no instance.render exists, and when this judgment is made, it indicates that the component does not get the render function from setup, and attempts the second behavior. Get the template from the Component, set the compile options and call Component.render = compile(template, finalCompilerOptions), which I covered in detail in my first article.

Finally, the compiled render function is assigned to the render property of the component instance, or NOOP null if not.

It then determines whether the render function is a run-time compiled render function wrapped with a with block, in which case it sets the render agent to a different HAS Handler agent trap, which is more efficient and avoids detecting global variables.

The component is initialized and the rendering function is set up.

export function finishComponentSetup(
  instance: ComponentInternalInstance,
  isSSR: boolean, skipOptions? :boolean
) {
  const Component = instance.type as ComponentOptions

  // Template/render function specification behavior
  // 1. The render function may already exist
  // 2, otherwise try using 'component. render' as a render function
  // 3. If this function has no render function, set 'instance.render' to null so that it can get render functions from mixins/extend
  if (__NODE_JS__ && isSSR) {
    instance.render = (instance.render ||
      Component.render ||
      NOOP) as InternalRenderFunction
  } else if(! instance.render) {// This can be set in setup()
    if(compile && ! Component.render) {const template = Component.template
      if (template) {
        const { isCustomElement, compilerOptions } = instance.appContext.config
        const {
          delimiters,
          compilerOptions: componentCompilerOptions
        } = Component
        const finalCompilerOptions: CompilerOptions = extend(
          extend(
            {
              isCustomElement,
              delimiters
            },
            compilerOptions
          ),
          componentCompilerOptions
        )
        Component.render = compile(template, finalCompilerOptions)
      }
    }

    instance.render = (Component.render || NOOP) as InternalRenderFunction

    // For run-time compiled rendering functions that use the 'with' block, this rendering agent requires a different 'has' handler trap, which is better
    // Only whitelisted globals attributes are allowed to pass.
    if (instance.render._rc) {
      instance.withProxy = new Proxy(
        instance.ctx,
        RuntimeCompiledPublicInstanceProxyHandlers
      )
    }
  }
}

Copy the code

conclusion

Today I introduced the process of initializing a stateful component. I covered it in detail in the Setup function initialization section. Not only did we learn the conditions for initializing a setup context, we also learned exactly what properties the setup context exposes to us, and we learned a new RFC proposal: Expose properties.

We learned how the Setup function executes and how Vue handles capturing the results of setup.

Finally we explain the component initialization, whether to use setup will execute finishComponentSetup function, through the internal logic of this function we understand a component in the initialization of the end, render function Settings rules.

If this article helps you understand the little details of setup in Vue3, I hope it gives you a thumbs up at ❤️. If you want to continue to follow the following articles, you can also follow my account, thank you again for reading so far.