zoukankan      html  css  js  c++  java
  • Vue-next源码新鲜出炉一

    vue3 作为目前最火的技术之一,除了学会使用以外,肯定是想在深入到源码里面,了解其实现原理,取其精华,提高自己的核心竞争力,无奈代码量太大,不知从何处下手,推荐开课吧花果山崔老师的mini-vue,虽然源码有些改动,但解读思路是一样的。

    准备工作

    1、TypeScript学习,Vue 3采用TS构建,学会TS很有必要
    2、ES6+相关知识,如 ProxyReflect 、Symbol、泛型等。

    ├── packages
    │   ├── compiler-core // 编译器核心
    │   ├── compiler-dom // dom解析&编译
    │   ├── compiler-sfc // 文件编译系统│   ├── compiler-ssr // 服务端渲染
    │   ├── reactivity // 数据响应
    │   ├── runtime-core // 虚拟DOM渲染-核心
    │   ├── runtime-dom // dom即时编译
    │   ├── runtime-test // 测试runtime
    │   ├── server-renderer // ssr
    │   ├── shared // 帮助
    │   ├── size-check // runtime包size检测
    │   ├── template-explorer
    │   └── vue // 构建vue
    

    这个流程将从用户使用的 api createApp 来作为入口点,分析vue 的内部执行流程

    1、createApp

    作用流程
    进行初始化,基于 rootComponent 生成 vnode, 进行 render
    image.png
    使用

    import { createApp } from 'vue'
    import App from './App.vue'
    
    const app = createApp(App)
    
    // 注册路由
    setupRouter(app)
    
    router.isReady().then(() => {
      app.mount('#app')
    })
    

    createAppAPI源码部分

    export function createAppAPI<HostElement>(
      render: RootRenderFunction,
      hydrate?: RootHydrateFunction
    ): CreateAppFunction<HostElement> {
      return function createApp(rootComponent, rootProps = null) {
        const context = createAppContext()
        //  当前 vue 的实例,全局唯一
        const app: App = (context.app = {
          // 属性
        	_uid: uid++,
          _component: rootComponent as ConcreteComponent, // 存放整个组件原始配置树
          _props: rootProps,
          _container: null,  // 根容器 , 虚拟dom 入口节点
          _context: context, //  上下文对象
          _instance: null,
          version,
          get config() {
            return context.config  // 全局配置
          },
          // 方法
          use(plugin: Plugin, ...options: any[]) {},
          mixin(mixin: ComponentOptions) {},
          component(name: string, component?: Component): any {},
          mount(
            rootContainer: HostElement,
            isHydrate?: boolean,
            isSVG?: boolean
          ): any {
               const vnode = createVNode(
                rootComponent as ConcreteComponent,
                rootProps
              )
            },
           unmount() {}
          provide(key, value) {}
        })
      	return app
      }
    }
    
    export function createAppContext(): AppContext {
      return {
        app: null as any,
        config: {
          isNativeTag: NO,
          performance: false,
          globalProperties: {},
          optionMergeStrategies: {},
          errorHandler: undefined,
          warnHandler: undefined,
          compilerOptions: {}
        },
        mixins: [],
        components: {},
        directives: {},
        provides: Object.create(null),
        optionsCache: new WeakMap(),
        propsCache: new WeakMap(),
        emitsCache: new WeakMap()
      }
    }
    

    抽象模拟

    import { render } from "./renderer";
    import { createVNode } from "./vnode";
    
    // createApp
    // 在 vue3 里面 createApp 是属于 renderer 对象的
    // 而 renderer 对象需要创建
    // 这里我们暂时不实现
    
    export const createApp = (rootComponent) => {
      const app = {
        _component: rootComponent,
        mount(rootContainer) {
          console.log("基于根组件创建 vnode");
          const vnode = createVNode(rootComponent);
          console.log("调用 render,基于 vnode 进行开箱");
          render(vnode, rootContainer);
        },
      };
    
      return app;
    };
    

    2、虚拟节点(VNode)

    1. VNode 表示虚拟节点 Virtual DOM,不是真的 DOM 节点,是对象用于描述节点的信息
    2. 他只是用 javascript 对象来描述真实 DOM,把DOM标签,属性,内容都变成对象的属性
    3. 过程就是,把template模板描述成 VNode,然后一系列操作之后通过 VNode 形成真实DOM进行挂载
    4. 虚拟 DOM:对由 Vue 组件树建立起来的整个 VNode 树的称呼,由 VNode 组成的

    什么作用:
    1、兼容性强,不受执行环境的影响。VNode 因为是 JS 对象,不管 Node 还是浏览器,都可以执行, 从而获得了服务端渲染、原生渲染、手写渲染函数等能力
    2、减少操作 DOM。任何页面的变化,都只使用 VNode 进行操作对比,只需要在最后一步挂载更新DOM,不需要频繁操作DOM,从而提高页面性能
    过程:
    模板 → 渲染函数 → 虚拟DOM树 → 真实DOM
    a675965c467fe86b1ea1ff344e2bd869.png

    源码部分

    位置 runtime-core/src/vnode.ts 文件

    export interface VNode<
      HostNode = RendererNode,
      HostElement = RendererElement,
      ExtraProps = { [key: string]: any }
    > {
      ...
    	// 内部属性
    }
    

    VNode本质是个对象,属性按照作用分为5类:
    image.png

    1、内部属性

    __v_isVNode: true // 标识是否为VNode
    [ReactiveFlags.SKIP]: true // 标识VNode不是observable
    type: VNodeTypes // VNode 类型
    props: (VNodeProps & ExtraProps) | null // 属性信息
    key: string | number | null // 特殊 attribute 主要用在 Vue 的虚拟 DOM 算法
    ref: VNodeNormalizedRef | null // 被用来给元素或子组件注册引用信息。
    scopeId: string | null // SFC only
    children: VNodeNormalizedChildren // 保存子节点
    component: ComponentInternalInstance | null // 指向VNode对应的组件实例
    dirs: DirectiveBinding[] | null // 保存应用在VNode的指令信息
    transition: TransitionHooks<HostElement> | null // 存储过渡效果信息
    

    2、DOM 属性

    el: HostNode | null // 真实DOM 节点
    anchor: HostNode | null // fragment anchor程序锚点
    target: HostElement | null // teleport target
    targetAnchor: HostNode | null // teleport target anchor
    staticCount: number // number of elements contained in a static vnode
    

    3、suspense 属性

    suspense: SuspenseBoundary | null
    ssContent: VNode | null
    ssFallback: VNode | null
    

    4、 optimization 属性(优化)

    shapeFlag: number
    patchFlag: number
    dynamicProps: string[] | null
    dynamicChildren: VNode[] | null
    

    5 、应用上下文属性

    appContext: AppContext | null
    ...
    

    创建 VNode

    Vue-Next提供了h函数,实际执行的就是createVNode(),由于频繁使用封装了一层

    
    // packages/runtime-core/src/h.ts
    // Actual implementation
    export function h(type: any, propsOrChildren?: any, children?: any): VNode {
      const l = arguments.length
      if (l === 2) {
        if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
          // single vnode without props
          if (isVNode(propsOrChildren)) {
            return createVNode(type, null, [propsOrChildren])
          }
          // props without children
          return createVNode(type, propsOrChildren)
        } else {
          // omit props
          return createVNode(type, null, propsOrChildren)
        }
      } else {
        if (l > 3) {
          children = Array.prototype.slice.call(arguments, 2)
        } else if (l === 3 && isVNode(children)) {
          children = [children]
        }
        return createVNode(type, propsOrChildren, children)
      }
    }
    

    h函数主要逻辑就是根据参数个数和参数类型,执行相应处理操作,调用 createVNode 函数来创建 VNode 对象
    开发实例

    app.component('componentHello', {
      template: "<div>Hello World!<div>"
    })
    

    编译一下

    import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
    
    export function render(_ctx, _cache, $props, $setup, $data, $options) {
      return (_openBlock(), _createElementBlock("div", null, "Hello World!"))
    }
    

    编译结果来看,生成一个render函数,执行createElementBlock()函数,是什么东东,一探究竟
    还是看源码吧

    export let currentBlock: VNode[]
    // packages/runtime-core/src/vnode.ts
    /**
     * @private
     */
    export function createElementBlock(
      type: string | typeof Fragment,
      props?: Record<string, any> | null,
      children?: any,
      patchFlag?: number,
      dynamicProps?: string[],
      shapeFlag?: number
    ) {
      return setupBlock(
        createBaseVNode(
          type,
          props,
          children,
          patchFlag,
          dynamicProps,
          shapeFlag,
          true /* isBlock */
        )
      )
    }
    
    function setupBlock(vnode: VNode) {
      // save current block children on the block vnode
      vnode.dynamicChildren =
        isBlockTreeEnabled > 0 ? currentBlock || (EMPTY_ARR as any) : null
      // close block
      closeBlock()
      // a block is always going to be patched, so track it as a child of its
      // parent block
      if (isBlockTreeEnabled > 0 && currentBlock) {
        currentBlock.push(vnode)
      }
      return vnode
    }
    
    
    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 = {
        __v_isVNode: true,
        __v_skip: true,
        type,
        props,
        key: props && normalizeKey(props),
        ref: props && normalizeRef(props),
        scopeId: currentScopeId,
        slotScopeIds: null,
        children,
        component: null,
        suspense: null,
        ssContent: null,
        ssFallback: null,
        dirs: null,
        transition: null,
        el: null,
        anchor: null,
        target: null,
        targetAnchor: null,
        staticCount: 0,
        shapeFlag,
        patchFlag,
        dynamicProps,
        dynamicChildren: null,
        appContext: null
      } as VNode
      
    ...
      return vnode
    }
    
    

    最后看到,还是生成 vnode放到 currentBlock中,实际作用还是创建是VNode。
    现在我们知道了createVNode的作用,接下来具体看看代码实现

    createVNode代码解读

    位置:runtime-core/src/vnode.ts
    代码量:89行+83行,分为两部分createVNode和 createBaseVNode(也叫createElementVNode)

    代码部分

    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 {
      
      /* 注意 type 有可能是 string 也有可能是对象
      * 如果是对象的话,那么就是用户设置的 options
      * type 为 string 的时候
      * createVNode("div")
      * type 为组件对象的时候
      */ createVNode(App)
      ...
      
      
       return createBaseVNode(
        type,
        props,
        children,
        patchFlag,
        dynamicProps,
        shapeFlag,
        isBlockNode,
        true
      )
    }
    
    createBaseVNode() // 上面已经出现过
    

    函数可以接收 6 个参数

    参数type

    export type VNodeTypes =
      | string 
      | VNode
      | Component
      | typeof Text
      | typeof Static
      | typeof Comment
      | typeof Fragment
      | typeof TeleportImpl
      | typeof SuspenseImpl
    
    // packages/runtime-core/src/vnode.ts
    export const Text = Symbol(__DEV__ ? 'Text' : undefined)
    export const Comment = Symbol(__DEV__ ? 'Comment' : undefined)
    export const Static = Symbol(__DEV__ ? 'Static' : undefined)
     
    export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
      __isFragment: true
      new (): {
        $props: VNodeProps
      }
    }
    

    image.png
    那么定义那么多的类型有什么意义呢?这是因为进行渲染render在 patch 阶段,基于 vnode 的类型进行不同类型的组件处理

    参数 props (Data & VNodeProps)

    // TypeScript 内置的工具类型 Record
    export type Data = Record<string, unknown>
      
    // 含有 key 和 ref 属性之外,其他的属性主要是定义了与生命周期有关的钩子
    export type VNodeProps = {
      key?: string | number | symbol
      ref?: VNodeRef
    
      // vnode hooks
      onVnodeBeforeMount?: VNodeMountHook | VNodeMountHook[]
      onVnodeMounted?: VNodeMountHook | VNodeMountHook[]
      onVnodeBeforeUpdate?: VNodeUpdateHook | VNodeUpdateHook[]
      onVnodeUpdated?: VNodeUpdateHook | VNodeUpdateHook[]
      onVnodeBeforeUnmount?: VNodeMountHook | VNodeMountHook[]
      onVnodeUnmounted?: VNodeMountHook | VNodeMountHook[]
    }
    

    主要逻辑

    // packages/runtime-core/src/vnode.ts
    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 {
      if (!type || type === NULL_DYNAMIC_COMPONENT) {
        if (__DEV__ && !type) {
          warn(`Invalid vnode type when creating vnode: ${type}.`)
        }
        type = Comment
      }
    
      // 处理VNode类型,比如处理动态组件的场景:<component :is="vnode"/>
      if (isVNode(type)) {
        const cloned = cloneVNode(type, props, true /* mergeRef: true */)
        if (children) {
          normalizeChildren(cloned, children)
        }
        return cloned
      }
    
     // 类组件规范化处理
      if (isClassComponent(type)) {
        type = type.__vccOpts
      }
    
      // 2.x 异步/功能组件兼容
      if (__COMPAT__) {
        type = convertLegacyComponent(type, currentRenderingInstance)
      }
    
      // 类和样式规范化处理
      if (props) {
        // for reactive or proxy objects, we need to clone it to enable mutation.
        props = guardReactiveProps(props)!
        let { class: klass, style } = props
        if (klass && !isString(klass)) {
          props.class = normalizeClass(klass)
        }
        if (isObject(style)) {
          // reactive state objects need to be cloned since they are likely to be
          // mutated
          if (isProxy(style) && !isArray(style)) {
            style = extend({}, style)
          }
          props.style = normalizeStyle(style)
        }
      }
    
       // 把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
    
      if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) {
        type = toRaw(type)
        warn(
          `Vue received a Component which was made a reactive object. This can ` +
            `lead to unnecessary performance overhead, and should be avoided by ` +
            `marking the component with \`markRaw\` or using \`shallowRef\` ` +
            `instead of \`ref\`.`,
          `
    Component that was made reactive: `,
          type
        )
      }
    
      // 创建VNode对象
      return createBaseVNode(
        type,
        props,
        children,
        patchFlag,
        dynamicProps,
        shapeFlag,
        isBlockNode,
        true
      )
    }
    

    1、createBaseVNode() 主要只作用是生成规范的VNode 对象,
    2、这里就要提到normalizeChildren函数,是一个递归函数,根据VNode,传入的children,修正VNode的type值和children的VNode

    export function normalizeChildren(vnode: VNode, children: unknown) {
      let type = 0
      const { shapeFlag } = vnode
      if (children == null) {
        children = null
      } else if (isArray(children)) {
        type = ShapeFlags.ARRAY_CHILDREN
      } else if (typeof children === 'object') {
        if (shapeFlag & (ShapeFlags.ELEMENT | ShapeFlags.TELEPORT)) {
          // Normalize slot to plain children for plain element and Teleport
          const slot = (children as any).default
          if (slot) {
            // _c marker is added by withCtx() indicating this is a compiled slot
            slot._c && (slot._d = false)
            normalizeChildren(vnode, slot())
            slot._c && (slot._d = true)
          }
          return
        } else {
          type = ShapeFlags.SLOTS_CHILDREN
          const slotFlag = (children as RawSlots)._
          if (!slotFlag && !(InternalObjectKey in children!)) {
            // if slots are not normalized, attach context instance
            // (compiled / normalized slots already have context)
            ;(children as RawSlots)._ctx = currentRenderingInstance
          } else if (slotFlag === SlotFlags.FORWARDED && currentRenderingInstance) {
            // a child component receives forwarded slots from the parent.
            // its slot type is determined by its parent's slot type.
            if (
              (currentRenderingInstance.slots as RawSlots)._ === SlotFlags.STABLE
            ) {
              ;(children as RawSlots)._ = SlotFlags.STABLE
            } else {
              ;(children as RawSlots)._ = SlotFlags.DYNAMIC
              vnode.patchFlag |= PatchFlags.DYNAMIC_SLOTS
            }
          }
        }
      } else if (isFunction(children)) {
        children = { default: children, _ctx: currentRenderingInstance }
        type = ShapeFlags.SLOTS_CHILDREN
      } else {
        children = String(children)
        // force teleport children to array so it can be moved around
        if (shapeFlag & ShapeFlags.TELEPORT) {
          type = ShapeFlags.ARRAY_CHILDREN
          children = [createTextVNode(children as string)]
        } else {
          type = ShapeFlags.TEXT_CHILDREN
        }
      }
      vnode.children = children as VNodeNormalizedChildren
      vnode.shapeFlag |= type
    }
    

    抽象模拟

    export const createVNode = function (
      type: any,
      props?: any = {},
      children?: string | Array<any>
    ) {
      // 注意 type 有可能是 string 也有可能是对象
      // 如果是对象的话,那么就是用户设置的 options
      // type 为 string 的时候
      // createVNode("div")
      // type 为组件对象的时候
      // createVNode(App)
      const vnode = {
        el: null,
        component: null,
        key: props.key || null,
        type,
        props,
        children,
        shapeFlag: getShapeFlag(type),
      };
    
      // 基于 children 再次设置 shapeFlag
      if (Array.isArray(children)) {
        vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN;
      } else if (typeof children === "string") {
        vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN;
      }
    
      normalizeChildren(vnode, children);
    
      return vnode;
    };
    
    export function normalizeChildren(vnode, children) {
      if (typeof children === "object") {
        // 暂时主要是为了标识出 slots_children 这个类型来
        // 暂时我们只有 element 类型和 component 类型的组件
        // 所以我们这里除了 element ,那么只要是 component 的话,那么children 肯定就是 slots 了
        if (vnode.shapeFlag & ShapeFlags.ELEMENT) {
          // 如果是 element 类型的话,那么 children 肯定不是 slots
        } else {
          // 这里就必然是 component 了,
          vnode.shapeFlag |= ShapeFlags.SLOTS_CHILDREN;
        }
      }
    }
    
  • 相关阅读:
    【★】IT界8大恐怖预言
    ★互联网告别免费时代,准备好了吗?
    ★互联网告别免费时代,准备好了吗?
    PS小实验-去除水印
    PS小实验-去除水印
    玩转PS路径,轻松画logo!
    玩转PS路径,轻松画logo!
    玩转PS路径,轻松画logo!
    地图收敛心得170405
    地图收敛心得170405
  • 原文地址:https://www.cnblogs.com/zzghk/p/15234767.html
Copyright © 2011-2022 走看看