A component option, executed before the component is created, once props is resolved, and serves as an entry point to the composite API

parameter

When you use the setup function, you take two arguments:

  • props:setupThe first argument in the function, reactive, when passed a newpropsWhen, it will be updated
  • content: context,setupThe second argument in the function is a plain JavaScript object that exposes the property of three components:attrs,slotsandemit

WARNINGS

  • becausepropsIs reactive, cannot use ES6 structure, because it will eliminatepropsIf you need structural props, you can use thesetupIn the functiontoRefsTo do this safely
  • contentIs a normal JavaScript object that is not reactive, meaning it can safely be usedcontentUse the ES6 architecture

Attrs and slots are stateful objects that are always updated as the component itself is updated. This means that you should avoid structuring them and always apply properties as attrs.x or slots.x. Note that, unlike props, attrs and slots are non-corresponding. If you are going to change application side effects based on attrs or slots, you should do so in the onUpdated lifecycle hook.

Start with a chestnut

const { createApp, setup } = Vue;

createApp({
    setup() {
        return {
            msg: 'hello, vue3~'
        }
    }
}).mount('#demo');
Copy the code

You can see the flow that triggers the setupComponent method: createApp -> createApp.mount -> mount -> render -> patch -> processComponent -> mountComponent -> setupComponent

There is a very large method baseCreateRenderer mentioned in the Vue3 initialization process. Some of the methods specifically implemented in the middle are skipped. Now let’s look at some of the processes and methods in detail:

// packages/runtime-core/src/renderer.ts
// overload 1: no hydration
function baseCreateRenderer<
  HostNode = RendererNode.HostElement = RendererElement> (options: RendererOptions<HostNode, HostElement>) :Renderer<HostElement>

// overload 2: with hydration
function baseCreateRenderer(
  options: RendererOptions<Node, Element>,
  createHydrationFns: typeof createHydrationFunctions
) :HydrationRenderer

// implementation
function baseCreateRenderer(options: RendererOptions, createHydrationFns? :typeof createHydrationFunctions
) :any {/ /... //Note: functions inside this closure should use `const xxx = () = >{} ` / /style in order to prevent being inlined by minifiers.
    const patch: PatchFn = (
        n1,
        n2,
        container,
        anchor = null,
        parentComponent = null,
        parentSuspense = null,
        isSVG = false,
        slotScopeIds = null,
        optimized = false
    ) = >{
        // patching & not same type, unmount old tree
        if(n1 && ! isSameVNodeType(n1, n2)) { anchor = getNextHostNode(n1) unmount(n1, parentComponent, parentSuspense,true)
            n1 = null
        }

        if (n2.patchFlag === PatchFlags.BAIL) {
            optimized = false
            n2.dynamicChildren = null
        }
        
        const { type, ref, shapeFlag } = n2
        switch (type) {
            case Text:
                processText(n1, n2, container, anchor)
                break
            case Comment:
                processCommentNode(n1, n2, container, anchor)
                break
            case Static:
                if (n1 == null) {
                    mountStaticNode(n2, container, anchor, isSVG)
                } else if (__DEV__) {
                    patchStaticNode(n1, n2, container, isSVG)
                }
                break
        case Fragment:
            processFragment(
                n1,
                n2,
                container,
                anchor,
                parentComponent,
                parentSuspense,
                isSVG,
                slotScopeIds,
                optimized
            )
            break
        default:
            if (shapeFlag & ShapeFlags.ELEMENT) {
                processElement(
                    n1,
                    n2,
                    container,
                    anchor,
                    parentComponent,
                    parentSuspense,
                    isSVG,
                    slotScopeIds,
                    optimized
                )
            } else if (shapeFlag & ShapeFlags.COMPONENT) {
                // processComponent
                processComponent(
                    n1,
                    n2,
                    container,
                    anchor,
                    parentComponent,
                    parentSuspense,
                    isSVG,
                    slotScopeIds,
                    optimized
                )
            } else if(shapeFlag & ShapeFlags.TELEPORT) { ; (typeas typeof TeleportImpl).process(
                    n1 as TeleportVNode,
                    n2 as TeleportVNode,
                    container,
                    anchor,
                    parentComponent,
                    parentSuspense,
                    isSVG,
                    slotScopeIds,
                    optimized,
                    internals
                )
            } else if(__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { ; (typeas typeof SuspenseImpl).process(
                    n1,
                    n2,
                    container,
                    anchor,
                    parentComponent,
                    parentSuspense,
                    isSVG,
                    slotScopeIds,
                    optimized,
                    internals
                )
            } else if (__DEV__) {
                warn('Invalid VNode type:', type, ` (The ${typeof type}) `)}}// set ref
        if(ref ! =null&& parentComponent) { setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, ! n2) } }// ...

    const processComponent = (
        n1: VNode | null,
        n2: VNode,
        container: RendererElement,
        anchor: RendererNode | null,
        parentComponent: ComponentInternalInstance | null,
        parentSuspense: SuspenseBoundary | null,
        isSVG: boolean,
        slotScopeIds: string[] | null,
        optimized: boolean
    ) = > {
        n2.slotScopeIds = slotScopeIds
        if (n1 == null) {
        if(n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) { ; (parentComponent! .ctxas KeepAliveContext).activate(
            n2,
            container,
            anchor,
            isSVG,
            optimized
            )
        } else {
            mountComponent(
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            optimized
            )
        }
        } else {
        updateComponent(n1, n2, optimized)
        }
    }
    
    const mountComponent: MountComponentFn = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) = > {
        // 2.x compat may pre-creaate the component instance before actually
        // mounting
        const compatMountInstance = __COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
        const instance: ComponentInternalInstance =
            compatMountInstance ||
            (initialVNode.component = createComponentInstance(
                initialVNode,
                parentComponent,
                parentSuspense
            ))

        if (__DEV__ && instance.type.__hmrId) {
            registerHMR(instance)
        }

        if (__DEV__) {
            pushWarningContext(initialVNode)
            startMeasure(instance, `mount`)}// inject renderer internals for keepAlive
        if(isKeepAlive(initialVNode)) { ; (instance.ctxas KeepAliveContext).renderer = internals
        }

        // resolve props and slots for setup context
        if(! (__COMPAT__ && compatMountInstance)) {if (__DEV__) {
                startMeasure(instance, `init`)}// setup
            setupComponent(instance)
            if (__DEV__) {
                endMeasure(instance, `init`)}}// setup() is async. This component relies on async logic to be resolved
        // before proceeding
        if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
            parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)

            // Give it a placeholder if this is not hydration
            // TODO handle self-defined fallback
            if(! initialVNode.el) {const placeholder = (instance.subTree = createVNode(Comment))
                processCommentNode(null, placeholder, container! , anchor) }return
        }

        setupRenderEffect(
        instance,
        initialVNode,
        container,
        anchor,
        parentSuspense,
        isSVG,
        optimized
        )

        if (__DEV__) {
            popWarningContext()
            endMeasure(instance, `mount`)}}/ /...
    
    return {
        render,
        hydrate,
        createApp: createAppAPI(render, hydrate)
    }
}
Copy the code

The setupComponent method in the mountComponent method starts to setup a set of functions. Since the Vue instance object has not been actually created, this in setup does not point to the Vue

// packages/runtime-core/src/component.ts
export function setupComponent(
  instance: ComponentInternalInstance,
  isSSR = false
) {
  isInSSRComponentSetup = isSSR

  const { props, children } = instance.vnode
  // Check component status
  const isStateful = isStatefulComponent(instance)
  // Initialize props
  initProps(instance, props, isStateful, isSSR)
  // Initialize slots
  initSlots(instance, children)

  // Create a state component or undefined according to isStateful state
  const setupResult = isStateful
    ? setupStatefulComponent(instance, isSSR)
    : undefined
  isInSSRComponentSetup = false
  return setupResult
}
Copy the code
// packages/runtime-core/src/component.ts
function setupStatefulComponent(instance: ComponentInternalInstance, isSSR: boolean) {
  const Component = instance.type as ComponentOptions

  if (__DEV__) {
    if (Component.name) {
      validateComponentName(Component.name, instance.appContext.config)
    }
    if (Component.components) {
      const names = Object.keys(Component.components)
      for (let i = 0; i < names.length; i++) {
        validateComponentName(names[i], instance.appContext.config)
      }
    }
    if (Component.directives) {
      const names = Object.keys(Component.directives)
      for (let i = 0; i < names.length; i++) {
        validateDirectiveName(names[i])
      }
    }
    if (Component.compilerOptions && isRuntimeOnly()) {
      warn(
        `"compilerOptions" is only supported when using a build of Vue that ` +
          `includes the runtime compiler. Since you are using a runtime-only ` +
          `build, the options should be passed via your build tool config instead.`)}}// 0. create render proxy property access cache
  instance.accessCache = Object.create(null)
  // 1. create public instance / render proxy
  // also mark it raw so it's never observed
  instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
  if (__DEV__) {
    exposePropsOnRenderContext(instance)
  }
  // 2. call 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) {
        // return the promise so server-renderer can wait on it
        return setupResult
          .then((resolvedResult: unknown) = > {
            handleSetupResult(instance, resolvedResult, isSSR)
          })
          .catch(e= > {
            handleError(e, instance, ErrorCodes.SETUP_FUNCTION)
          })
      } else if (__FEATURE_SUSPENSE__) {
        // async setup returned Promise.
        // bail here and wait for re-entry.
        instance.asyncDep = setupResult
      } else if (__DEV__) {
        warn(
          `setup() returned a Promise, but the version of Vue you are using ` +
            `does not support it yet.`)}}else {
      handleSetupResult(instance, setupResult, isSSR)
    }
  } else {
    finishComponentSetup(instance, isSSR)
  }
}
Copy the code

Create the instance and setupResult in the setupStatefulComponent method, initialize it and call handleSetupResult

// packages/runtime-core/src/component.ts
export function handleSetupResult(instance: ComponentInternalInstance, setupResult: unknown, isSSR: boolean) {
  if (isFunction(setupResult)) {
    // setup returned an inline render function
    if (__NODE_JS__ && (instance.type as ComponentOptions).__ssrInlineRender) {
      // when the function's name is `ssrRender` (compiled by SFC inline mode),
      // set it as ssrRender instead.
      instance.ssrRender = setupResult
    } else {
      instance.render = setupResult as InternalRenderFunction
    }
  } else if (isObject(setupResult)) {
    if (__DEV__ && isVNode(setupResult)) {
      warn(
        `setup() should not return VNodes directly - ` +
          `return a render function instead.`)}// setup returned bindings.
    // assuming a render function compiled from template is present.
    if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
      instance.devtoolsRawSetupState = setupResult
    }
    instance.setupState = proxyRefs(setupResult)
    if (__DEV__) {
      exposeSetupStateOnRenderContext(instance)
    }
  } else if(__DEV__ && setupResult ! = =undefined) {
    warn(
      `setup() should return an object. Received: ${
        setupResult === null ? 'null' : typeof setupResult
      }`
    )
  }
  finishComponentSetup(instance, isSSR)
}
Copy the code

Call finishConponentSetup in handleSetupResult

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

  if (__COMPAT__) {
    convertLegacyRenderFn(instance)

    if (__DEV__ && Component.compatConfig) {
      validateCompatConfig(Component.compatConfig)
    }
  }

  // template / render function normalization
  if (__NODE_JS__ && isSSR) {
    // 1. the render function may already exist, returned by `setup`
    // 2. otherwise try to use the `Component.render`
    // 3. if the component doesn't have a render function,
    // set `instance.render` to NOOP so that it can inherit the render
    // function from mixins/extend
    instance.render = (instance.render ||
      Component.render ||
      NOOP) as InternalRenderFunction
  } else if(! instance.render) {// could be set from setup()
    if(compile && ! Component.render) {const template =
        (__COMPAT__ &&
          instance.vnode.props &&
          instance.vnode.props['inline-template']) ||
        Component.template
      if (template) {
        if (__DEV__) {
          startMeasure(instance, `compile`)}const { isCustomElement, compilerOptions } = instance.appContext.config
        const {
          delimiters,
          compilerOptions: componentCompilerOptions
        } = Component
        const finalCompilerOptions: CompilerOptions = extend(
          extend(
            {
              isCustomElement,
              delimiters
            },
            compilerOptions
          ),
          componentCompilerOptions
        )
        if (__COMPAT__) {
          // pass runtime compat config into the compiler
          finalCompilerOptions.compatConfig = Object.create(globalCompatConfig)
          if (Component.compatConfig) {
            extend(finalCompilerOptions.compatConfig, Component.compatConfig)
          }
        }
        // Compile the template template
        Component.render = compile(template, finalCompilerOptions)
        if (__DEV__) {
          endMeasure(instance, `compile`)
        }
      }
    }

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

    // for runtime-compiled render functions using `with` blocks, the render
    // proxy used needs a different `has` handler which is more performant and
    // also only allows a whitelist of globals to fallthrough.
    if (instance.render._rc) {
      instance.withProxy = new Proxy(
        instance.ctx,
        RuntimeCompiledPublicInstanceProxyHandlers
      )
    }
  }

  // support for 2.x options
  if(__FEATURE_OPTIONS_API__ && ! (__COMPAT__ && skipOptions)) { currentInstance = instance pauseTracking() applyOptions(instance) resetTracking() currentInstance =null
  }

  // warn missing template/render
  // the runtime compilation of template in SSR is done by server-render
  if(__DEV__ && ! Component.render && instance.render === NOOP && ! isSSR) {/* istanbul ignore if */
    if(! compile && Component.template) { warn(`Component provided template option but ` +
          `runtime compilation is not supported in this build of Vue.` +
          (__ESM_BUNDLER__
            ? ` Configure your bundler to alias "vue" to "vue/dist/vue.esm-bundler.js".`
            : __ESM_BROWSER__
              ? ` Use "vue.esm-browser.js" instead.`
              : __GLOBAL__
                ? ` Use "vue.global.js" instead.`
                : ` `) /* should not happen */)}else {
      warn(`Component is missing template or render function.`)}}}Copy the code

In the finishComponentSetup method, the compiler starts to compile after the template string and compile-related configuration are processed. It can be seen from the source code that there are compatible writing methods for VUe2, so the writing method for VUe2 is supported in setup. The setup process is complete when the finishComponentSetup method finishes.

Conclusion:

  • setupIt is executed before all components are created and is treated as the master entry to all apis
  • setupThe vUE instance object was created before the other components, so it is the same as the vUE instance object in vue2thisThere is a difference
  • setupAs a syntactic sugar contains you can pass other hook functions