vue3源码学习4-setup启动函数

vue3的单文件代码如下:

<template>
  <div>
  <div>{{msg}}</div>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  setup () {
    const msg = ref('哈喽!')
    return {
      msg
    }
  }
}
</script>

模板访问的msg变量包含在Composition API 入口的setup函数中,他们之间的联系是怎么实现的?

packages/runtime-core/src/renderer.ts

我们之前看过组件的渲染流程:创建vnode->渲染vnode->DOM,渲染vode的过程主要是挂载组件:

const mountComponent: MountComponentFn = (
    initialVNode,
    container,
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    optimized
  ) => {
	// 创建组件实例
	const instance: ComponentInternalInstance =
      compatMountInstance ||
      (initialVNode.component = createComponentInstance(
        initialVNode,
        parentComponent,
        parentSuspense
      ))
	// 设置组件实例
	setupComponent(instance)
	// 设置并运行带副作用的渲染函数
	setupRenderEffect(
      instance,
      initialVNode,
      container,
      anchor,
      parentSuspense,
      isSVG,
      optimized
    )

这一小节,我们只看创建组件实例和设置组件实例,首先创建组件实例createComponentInstance方法在packages/runtime-core/src/component.ts文件中:

export function createComponentInstance(
  vnode: VNode,
  parent: ComponentInternalInstance | null,
  suspense: SuspenseBoundary | null
) {
  const type = vnode.type as ConcreteComponent
  // inherit parent app context - or - if root, adopt from root vnode
  // 继承父组件实例上的 appContext, 如果是根组件,则直接从根vnode中获取
  const appContext =
    (parent ? parent.appContext : vnode.appContext) || emptyAppContext

  const instance: ComponentInternalInstance = {
	// 组件唯一 id
    uid: uid++,
	// 组件 vnode
    vnode,
	// vnode节点类型
    type,
	// 父组件实例
    parent,
	// app上下文
    appContext,
	// 根组件实例
    root: null!, // to be immediately set
	// 新的组件 vnode
    next: null,
	// 子节点 vnode
    subTree: null!, // will be set synchronously right after creation
	// 响应式相关对象
    effect: null!,
	// 带副作用的更新函数
    update: null!, // will be set synchronously right after creation
	// 作用域
    scope: new EffectScope(true /* detached */),
	// 渲染函数
    render: null,
	// 渲染上下文代理
    proxy: null,
    exposed: null,
    exposeProxy: null,
	// 带有with区块的渲染上下文代理
    withProxy: null,
	// 依赖注入相关
    provides: parent ? parent.provides : Object.create(appContext.provides),
	// 渲染代理的属性访问缓存
    accessCache: null!,
	// 渲染缓存
    renderCache: [],

    // local resolved assets
	// 注册的组件
    components: null,
	// 注册的指令
    directives: null,

    // resolved props and emits options
    propsOptions: normalizePropsOptions(type, appContext),
    emitsOptions: normalizeEmitsOptions(type, appContext),

    // emit
	// 派发事件方法
    emit: null!, // to be set immediately
    emitted: null,

    // props default value
    propsDefaults: EMPTY_OBJ,

    // inheritAttrs
    inheritAttrs: type.inheritAttrs,

    // state
	// 渲染上下文
    ctx: EMPTY_OBJ,
	// data数据
    data: EMPTY_OBJ,
	// props数据
    props: EMPTY_OBJ,
	// 普通属性
    attrs: EMPTY_OBJ,
	// 插槽相关
    slots: EMPTY_OBJ,
	// 组件或者DOM的ref引用
    refs: EMPTY_OBJ,
	// setup 函数返回的响应式结果
    setupState: EMPTY_OBJ,
	// setup 函数的上下文数据
    setupContext: null,

    // suspense related
	// suspense 相关
    suspense,
    suspenseId: suspense ? suspense.pendingId : 0,
	// suspense 异步依赖
    asyncDep: null,
	// suspense 异步依赖是否都已处理
    asyncResolved: false,

    // lifecycle hooks
    // not using enums here because it results in computed properties
	// 是否挂载
    isMounted: false,
	// 是否卸载
    isUnmounted: false,
	// 是否激活
    isDeactivated: false,
	// 声明周期 befor create
    bc: null,
	// 声明周期 created
    c: null,
	// 声明周期 befor mount
    bm: null,
	// 声明周期 mounted
    m: null,
	// 声明周期 befor update
    bu: null,
	// 声明周期 updated
    u: null,
	// 声明周期 unmounted
    um: null,
	// 声明周期 befor unmount
    bum: null,
	// 声明周期 deactivated
    da: null,
	// 声明周期 activated
    a: null,
	// render triggered
    rtg: null,
	// render traked
    rtc: null,
	// error captured
    ec: null,
    sp: null
  }
  if (__DEV__) {
    instance.ctx = createDevRenderContext(instance)
  } else {
	// 初始化渲染上下文
    instance.ctx = { _: instance }
  }
  // 初始化根组件指针
  instance.root = parent ? parent.root : instance
  // 初始化派发事件方法
  instance.emit = emit.bind(null, instance)

  // apply custom element special handling
  if (vnode.ce) {
    vnode.ce(instance)
  }

  return instance
}

接着在同文件看设置组件实例:

export function setupComponent(
  instance: ComponentInternalInstance,
  isSSR = false
) {
  isInSSRComponentSetup = isSSR

  const { props, children } = instance.vnode
  // 是否是有状态的组件
  const isStateful = isStatefulComponent(instance)
  // 初始化props
  initProps(instance, props, isStateful, isSSR)
  // 初始化插槽
  initSlots(instance, children)
  // 设置有状态的组件实例
  const setupResult = isStateful
    ? setupStatefulComponent(instance, isSSR)
    : undefined
  isInSSRComponentSetup = false
  return setupResult
}

initProps和initSlots后面再看,现在继续看setupStatefulComponent方法

function setupStatefulComponent(
  instance: ComponentInternalInstance,
  isSSR: boolean
) {
  const Component = instance.type as ComponentOptions
  // 0. create render proxy property access cache
  // 0.创建渲染代理的属性访问缓存
  instance.accessCache = Object.create(null)
  // 1. create public instance / render proxy
  // 1.创建渲染上下文代理
  // also mark it raw so it's never observed
  instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
  // 2. call setup()
  // 2.判断处理setup函数
  const { setup } = Component
  if (setup) {
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null)
	// 执行setup函数,获取结果
	const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
    )
	// 处理setup执行结果
	handleSetupResult(instance, setupResult, isSSR)
  } else {
	// 完成组件实例设置
	finishComponentSetup(instance, isSSR)
  }
}

接下来看创建渲染上下文代理,instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers)),我们分析proxy的get、set和has方法,首先当我们访问instance.ctx(渲染实例的上下文)中的属性时,就会走get函数:

export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
  get({ _: instance }: ComponentRenderContext, key: string) {
     const { ctx, setupState, data, props, accessCache, type, appContext } = instance
	  // 首先判断key不以$开头,这部分数据可能是setupState、data、props、ctx中的一种,data和props在开发中经常使用,
	  // setupStata 就是setup函数返回的数据,ctx包括了计算属性、组件方法和用户自定义的数据。
	  // 如果不以$开头,会依次判断setupState、data、props、ctx中是否包含这个key,如果包含就返回对应值,这个顺序决定在key相
	  // 同时,取值的优先级
	 if (key[0] !== '$') {
	  // 渲染代理的属性访问缓存中
      const n = accessCache![key]
      if (n !== undefined) {
		// 从缓存中取值
        switch (n) {
          case AccessTypes.SETUP:
            return setupState[key]
          case AccessTypes.DATA:
            return data[key]
          case AccessTypes.CONTEXT:
            return ctx[key]
          case AccessTypes.PROPS:
            return props![key]
          // default: just fallthrough
        }
      } else if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
        accessCache![key] = AccessTypes.SETUP
		// 从setupState取
        return setupState[key]
      } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
        accessCache![key] = AccessTypes.DATA
		// 从data中取
        return data[key]
      } else if (
        // only cache other properties when instance has declared (thus stable)
        // props
        (normalizedProps = instance.propsOptions[0]) &&
        hasOwn(normalizedProps, key)
      ) {
        accessCache![key] = AccessTypes.PROPS
		// 从props中取
        return props![key]
      } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
        accessCache![key] = AccessTypes.CONTEXT
		// 从ctx取
        return ctx[key]
      } else if (!__FEATURE_OPTIONS_API__ || shouldCacheAccess) {
		// 都取不到
        accessCache![key] = AccessTypes.OTHER
      }
    }
	const publicGetter = publicPropertiesMap[key]
    let cssModule, globalProperties
    // public $xxx properties
	// 公开的 $xxx 属性或方法
    if (publicGetter) {
      if (key === '$attrs') {
        track(instance, TrackOpTypes.GET, key)
        __DEV__ && markAttrsAccessed()
      }
      return publicGetter(instance)
    } else if (
      // css module (injected by vue-loader)
	  // css 模块,通过vue-loader编译时注入
      (cssModule = type.__cssModules) &&
      (cssModule = cssModule[key])
    ) {
      return cssModule
    } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
      // user may set custom properties to `this` that start with `$`
	  // 用户自定义属性,也用$开头
      accessCache![key] = AccessTypes.CONTEXT
      return ctx[key]
    } else if (
      // global properties
	  // 全局定义的属性
      ((globalProperties = appContext.config.globalProperties),
      hasOwn(globalProperties, key))
    ) {
      if (__COMPAT__) {
        const desc = Object.getOwnPropertyDescriptor(globalProperties, key)!
        if (desc.get) {
          return desc.get.call(instance.proxy)
        } else {
          const val = globalProperties[key]
          return isFunction(val)
            ? Object.assign(val.bind(instance.proxy), val)
            : val
        }
      } else {
        return globalProperties[key]
      }
    } else if (
      __DEV__ &&
      currentRenderingInstance &&
      (!isString(key) ||
        // #1091 avoid internal isRef/isVNode checks on component instance leading
        // to infinite warning loop
        key.indexOf('__v') !== 0)
    ) {
      if (data !== EMPTY_OBJ && isReservedPrefix(key[0]) && hasOwn(data, key)) {
		// 如果在data中定义的数据以$开头,会报警告,以为$是保留字符,不会做代理
        warn(
          `Property ${JSON.stringify(
            key
          )} must be accessed via $data because it starts with a reserved ` +
            `character ("$" or "_") and is not proxied on the render context.`
        )
      } else if (instance === currentRenderingInstance) {
		// 在模板中使用的变量没有定义,报警告
        warn(
          `Property ${JSON.stringify(key)} was accessed during render ` +
            `but is not defined on instance.`
        )
      }
    }
  },
}

接下来是set代理的过程,当修改instance.ctx渲染上下文中的属性时,就会进入set函数:

export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
  set(
    { _: instance }: ComponentRenderContext,
    key: string,
    value: any
  ): boolean {
    const { data, setupState, ctx } = instance
    if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
	  // 给setupState 赋值
      setupState[key] = value
      return true
    } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
	  // 给data 赋值
      data[key] = value
      return true
    } else if (hasOwn(instance.props, key)) {
      __DEV__ &&
		// 不能给props赋值
        warn(
          `Attempting to mutate prop "${key}". Props are readonly.`,
          instance
        )
      return false
    }
    if (key[0] === '$' && key.slice(1) in instance) {
      __DEV__ &&
		// 不能给Vue内部以$开头的保留属性赋值
        warn(
          `Attempting to mutate public property "${key}". ` +
            `Properties starting with $ are reserved and readonly.`,
          instance
        )
      return false
    } else {
      if (__DEV__ && key in instance.appContext.config.globalProperties) {
        Object.defineProperty(ctx, key, {
          enumerable: true,
          configurable: true,
          value
        })
      } else {
		// 用户自定义数据赋值
        ctx[key] = value
      }
    }
    return true
  },
}

set函数主要是对渲染上下文instance.ctx中的属性赋值,实际上是代理到对应的数据类型中去完成赋值操作,从代码顺序能看到,优先判断的setupState,然后是data,最后是props和用户自定义的数据。

当我们判断属性是否存在于instance.ctx渲染的上下文中,就会进入has函数,例如:

export default {
  created () {
    console.log('msg' in this)
  }
}

has函数的实现:

export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
  has(
    {
      _: { data, setupState, accessCache, ctx, appContext, propsOptions }
    }: ComponentRenderContext,
    key: string
  ) {
    let normalizedProps
	// 依次判断key是否在accessCache、data、setupState、props、用户数据、公开属性、全局属性
    return (
      !!accessCache![key] ||
      (data !== EMPTY_OBJ && hasOwn(data, key)) ||
      (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) ||
      ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||
      hasOwn(ctx, key) ||
      hasOwn(publicPropertiesMap, key) ||
      hasOwn(appContext.config.globalProperties, key)
    )
  },
}

然后回到setupStatefulComponent,看一下第二步的判断处理setup函数过程,也就是下面的代码:

const { setup } = Component
  if (setup) {
    const setupContext = (instance.setupContext =
      setup.length > 1 ? createSetupContext(instance) : null)
	// 执行setup函数,获取结果
	const setupResult = callWithErrorHandling(
      setup,
      instance,
      ErrorCodes.SETUP_FUNCTION,
      [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
    )
	// 处理setup执行结果
	handleSetupResult(instance, setupResult, isSSR)
  } 

首先判断setup函数的参数长度,如果大于1,则创建setupContext上下文:

// 创建setupContext上下文函数中,返回了一个对象,让我们在setup中可以获取到组件的属性、插槽和emit
export function createSetupContext(
  instance: ComponentInternalInstance
): SetupContext {
	return {
      get attrs() {
        return attrs || (attrs = createAttrsProxy(instance))
      },
      slots: instance.slots,
      emit: instance.emit,
      expose
    }
}

然后执行setup函数并返回结果callWithErrorHandling

// 其实就是对fn做了一层包装,在有参数的时候传入参数,setup的第一个参数是instance.props,第二个参数是setupContext
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
}

最后处理setup执行结果handleSetupResult:

export function handleSetupResult(
  instance: ComponentInternalInstance,
  setupResult: unknown,
  isSSR: boolean
) {
  if (isFunction(setupResult)) {
    // setup returned an inline render function
	// 当模板使用h函数渲染时,setup返回一个函数作为组件的渲染函数
    instance.render = setupResult as InternalRenderFunction
  } else if (isObject(setupResult)) {
    // setup returned bindings.
    // assuming a render function compiled from template is present.
	// 如果setupResult是对象,把它变成响应式并赋值给instance.setupState,这样模板渲染的时候,
	// 根据前面get方法的获取规则,instance.ctx就能从instance.setupState上获取到对应的数据
    instance.setupState = proxyRefs(setupResult)
  }
  finishComponentSetup(instance, isSSR)
}

处理完成后,会执行finishComponentSetup去完成组件实例的设置

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

  // template / render function normalization
  // could be already set when returned from setup()
  // 对模板或者渲染函数标准化,主要考虑:
	// 1.compile 和组件 template 属性存在,render 方法不存在的情况。
	// 2.compile 和 render 方法不存在,组件 template 属性存在的情况。
	// 3.组件既没有写 render 函数,也没有写 template 模板,此时要报一个警告
  if (!instance.render) {
    // only do on-the-fly compile if not in SSR - SSR on-the-fly compilation
    // is done by server-renderer
    if (!isSSR && 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) {
            // @ts-expect-error types are not compatible
            extend(finalCompilerOptions.compatConfig, Component.compatConfig)
          }
        }
        Component.render = compile(template, finalCompilerOptions)
        if (__DEV__) {
          endMeasure(instance, `compile`)
        }
      }
    }

	// 组件对象的 render 函数赋值给 instance
    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 (installWithProxy) {
      installWithProxy(instance)
    }
  }

  // support for 2.x options
  // 兼容Vue2.x的Options API
  if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
    setCurrentInstance(instance)
    pauseTracking()
    applyOptions(instance)
    resetTracking()
    unsetCurrentInstance()
  }
}
posted @ 2022-09-05 15:19  菜菜123521  阅读(296)  评论(0编辑  收藏  举报