Vue3追本溯源(二)双向数据绑定

·  阅读 584
Vue3追本溯源(二)双向数据绑定

本篇接上篇入口函数,以一个简单的事例(后续称之为本例)详细解析mount之后的操作(解析传入的options参数、template模版编译、生成虚拟DOM对象、虚拟DOM对象解析生成真正的DOM、数据修改之后如何触发DOM的重新渲染等等)

简单事例(以Vue3新增的setup方法为例)

<div id='app'>
  {{message}}
  <button @click="modifyMessage">修改数据</button>
</div>
复制代码
const { ref, createApp } = Vue
const app = createApp({
  // Vue3新增的setup属性
  setup(props) {
    const message = ref('测试数据')
    const modifyMessage = () => {
      message.value = '修改后的测试数据'
    }
    return {
      message,
      modifyMessage
    }
  }
}).mount('#app')
复制代码

app.mount

const proxy = mount(container, false, container instanceof SVGElement)
复制代码

Vue3通过createApp方法生成了app对象,接着调用app.mount方法获取了root根节点,然后调用原始的mount方法进行挂载,来看下初始的mount内部实现

// context对象的生成
const context = createAppContext()
// 原始的mount函数
mount(
  rootContainer: HostElement,
  isHydrate?: boolean,
  isSVG?: boolean
): any {
  if (!isMounted) {
    const vnode = createVNode(
      rootComponent as ConcreteComponent,
      rootProps
    )
    vnode.appContext = context
    // ...
    if (isHydrate && hydrate) {
      hydrate(vnode as VNode<Node, Element>, rootContainer as any)
    } else {
      render(vnode, rootContainer, isSVG)
    }
    isMounted = true
    app._container = rootContainer
    (rootContainer as any).__vue_app__ = app
    // ...
    return vnode.component!.proxy
  }
}
复制代码

mount方法首先调用createVNode方法创建vnode对象,简单看下createVNode方法的内部实现(主要是看下shapeFlag变量的生成,与后续调用patch方法关联)

export const createVNode = (
  __DEV__ ? createVNodeWithArgsTransform : _createVNode
) as typeof _createVNode

function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag: number = 0,
  dynamicProps: string[] | null = null,
  isBlockNode = false
): VNode {
  // ...
  const shapeFlag = isString(type)
    ? ShapeFlags.ELEMENT
    : __FEATURE_SUSPENSE__ && isSuspense(type)
    ? ShapeFlags.SUSPENSE
    : isTeleport(type)
    ? ShapeFlags.TELEPORT
    : isObject(type)
    ? ShapeFlags.STATEFUL_COMPONENT
    : isFunction(type)
    ? ShapeFlags.FUNCTIONAL_COMPONENT
    : 0
  // ...
  return createBaseVNode(
    type,
    props,
    children,
    patchFlag,
    dynamicProps,
    shapeFlag,
    isBlockNode,
    true
  )
}

// createBaseVNode方法
function createBaseVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag = 0,
  dynamicProps: string[] | null = null,
  shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
  isBlockNode = false,
  needFullChildrenNormalization = false
) {
    const vnode = {
        type,
        key,
        props,
        shapeFlag,
        patchFlag,
        // ...
    }
    // ...
    return vnode
}
复制代码

可以看到shapeFlag是根据调用此方法的rootComponent对象也就是我们调用createApp方法传入的参数类型决定的。本例中我们传入的是一个对象,所以shapeFlag的值为ShapeFlags.STATEFUL_COMPONENT(1 << 2 = 4)。之后设置vnodeappContext的属性为context对象(该对象是调用createAppContext方法生成的,后续使用到context对象属性时再详细解析)。接着我们再回到mount方法

if (isHydrate && hydrate) {
  hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
  render(vnode, rootContainer, isSVG)
}
复制代码

因为传入的isHydratefalse,所以调用render方法,参数为vnode对象、root根节点以及false(isSVGcontainer instanceof SVGElement的值,本例并非svg元素)。调用的render函数是createAppAPI方法的传参,而createAppAPI方法是在baseCreateRenderer方法中调用的。回归到baseCreateRenderer方法中查看render方法

const render: RootRenderFunction = (vnode, container, isSVG) => {
  // vnode是存在的
  if (vnode == null) {
    if (container._vnode) {
      unmount(container._vnode, null, null, true)
    }
  } else {
    patch(container._vnode || null, vnode, container, null, null, null, isSVG)
  }
  // ...
}
复制代码

使用patch解析根组件

因为vnode是存在的,所以调用patch方法

patch(null, vnode, container, null, null, null, false)

// patch方法
const patch: PatchFn = (
  n1,
  n2,
  container,
  anchor = null,
  parentComponent = null,
  parentSuspense = null,
  isSVG = false,
  slotScopeIds = null,
  optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
) => {
  // ...
  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) {
          // ...
        } else if (shapeFlag & ShapeFlags.COMPONENT) {
          processComponent(
            n1,
            n2,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized
          )
        } else if (shapeFlag & ShapeFlags.TELEPORT) {
          // ...
        } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
          // ...
        } else if (__DEV__) {
          warn('Invalid VNode type:', type, `(${typeof type})`)
        }
    }
}
复制代码

patch方法会根据type的值选择调用函数,本例type{setup: setup(props){}}是一个对象,所以进入default分支,因为vnodeshapeFlag刚才解析为4(4 & (1 << 2 | 1 << 1) = 4),因此调用processComponent方法(处理组件)

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) {
        // 组件为keep-alive
        if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {}
        else {
            mountComponent(
              n2,
              container,
              anchor,
              parentComponent,
              parentSuspense,
              isSVG,
              optimized
            )
      }
    } else {}
}
复制代码

挂载根组件

之后调用mountComponent方法

const mountComponent: MountComponentFn = (
    initialVNode,
    container,
    anchor,
    parentComponent,
    parentSuspense,
    isSVG,
    optimized
) => {
    const compatMountInstance =
      __COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
    const instance: ComponentInternalInstance =
      compatMountInstance ||
      (initialVNode.component = createComponentInstance(
        initialVNode,
        parentComponent,
        parentSuspense
      ))
    // ...
    // resolve props and slots for setup context
    if (!(__COMPAT__ && compatMountInstance)) {
      if (__DEV__) {
        startMeasure(instance, `init`)
      }
      setupComponent(instance)
      if (__DEV__) {
        endMeasure(instance, `init`)
      }
    }
    // ...
    setupRenderEffect(
      instance,
      initialVNode,
      container,
      anchor,
      parentSuspense,
      isSVG,
      optimized
    )
    // ...
}
复制代码

内部主要是通过createComponentInstance方法创建instance对象(创建一个对象,并利用Object.defineProperty给对象增加_属性,值即为instance对象,对象的vnode属性即为调用时传入的vnode。还有一些其他的属性,后续使用到再详细解析)。然后调用setupComponent(instance)方法。

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

  const setupResult = isStateful
    ? setupStatefulComponent(instance, isSSR)
    : undefined
  isInSSRComponentSetup = false
  return setupResult
}

function setupStatefulComponent(
  instance: ComponentInternalInstance,
  isSSR: boolean
) {
    // ...
    instance.accessCache = Object.create(null)
    instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
    // 2. call setup()
    const { setup } = Component
    if (setup) {
        // ...
        const setupResult = callWithErrorHandling(
          setup,
          instance,
          ErrorCodes.SETUP_FUNCTION,
          [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
        )
        // ...
    }
}
复制代码

setupComponent方法主要调用了setupStatefulComponent方法,此方法的内部主要是将instance.ctx使用new Proxy()代理,instance.ctx对象中是_ 、$、$el等属性,而代理的钩子函数对象是PublicInstanceProxyHandlers

export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
    get({ _: instance }: ComponentRenderContext, key: string) {},
    set(
        { _: instance }: ComponentRenderContext,
        key: string,
        value: any
      ): boolean {},
    has(
        {
          _: { data, setupState, accessCache, ctx, appContext, propsOptions }
        }: ComponentRenderContext,
        key: string
    ) {}
}
复制代码

PublicInstanceProxyHandlers包含了get、set、has三个陷阱钩子函数,后续触发时我们再详细解析。回到setupStatefulComponent函数中,当setup属性存在时(本例存在),开始运行setup函数,解析函数体中的数据及方法。

总结

原始的mount挂载方法中主要是利用createVNode创建了vnode节点,参数type是调用createApp方法传入的,根据type的类型解析出shapeFlag。然后调用patch方法,根据shapeFlag的值判断是调用哪个方法。解析组件时调用mountComponent方法,该方法中创建了instance对象,其中对象的ctx属性新增了_、$、$el等属性,instance对象的vnode属性即为创建的vnode对象,而vnode对象的component属性为instance对象,二者相互关联。之后利用new Proxy代理instance.ctx对象,设置get、set、has钩子函数。之后会解析setup方法,看看是如何给传入的数据设置钩子函数的。

分类:
前端
标签: