• Vue3全局APi解析-源码学习


    本文章共5314字,预计阅读时间5-15分钟。

    前言

    不知不觉Vue-next的版本已经来到了3.1.2,最近对照着源码学习Vue3的全局Api,边学习边整理了下来,希望可以和大家一起进步。

    我们以官方定义、用法、源码浅析三个维度来一起看看它们。

    下文是关于Vue3全局Api的内容,大家如果有更好的理解和想法,可以在评论区留言,每条我都会回复~

    全局API

    全局API是直接在Vue上挂载方法,在Vue中,全局API一共有13个。分别是:

    • createapp 返回一个提供应用上下文的应用实例;
    • h 返回一个”虚拟节点;
    • definecomponent 返回options的对象,在TS下,会给予组件正确的参数类型推断;
    • defineasynccomponent 创建一个只有在需要时才会加载的异步组件;
    • resolvecomponent 按传入的组件名称解析 component;
    • resolvedynamiccomponent 返回已解析的Component或新建的VNode;
    • resolvedirective 通过其名称解析一个 directive;
    • withdirectives 返回一个包含应用指令的 VNode;
    • createrenderer 跨平台自定义渲染;
    • nexttick 是将回调函数延迟在下一次dom更新数据后调用;
    • mergeprops 将包含 VNode prop 的多个对象合并为一个单独的对象;
    • usecssmodule 访问 CSS 模块;
    • version 查看已安装的 Vue 的版本号;

    createApp

    官方定义:返回一个提供应用上下文的应用实例。应用实例挂载的整个组件树共享同一个上下文。

    顾名思义,CreateApp 作为 vue 的启动函数,返回一个应用实例,每个 Vue 应用程序都首先使用以下函数创建一个新的应用程序实例,应用程序实例公开的大多数方法都返回相同的实例,可以链式调用。例如:

    Vue.createApp({}).component('SearchInput', SearchInputComponent)
    

    用法

    • 第一个参数: 接收一个根组件选项

    • 第二个参数: 将根 prop 传递给应用程序

    // 用法示例
    import { createApp, h, nextTick } from 'vue'
    const app = createApp({
      data() {
        return {
          ...
        }
      },
      methods: {...},
      computed: {...}
      ...
    },
        { username: 'Evan' })
    

    源码浅析

    GitHub地址:

    // 源码位置上方[1]
    export const createApp = ((...args) => {
        // 使用ensureRenderer().createApp() 来创建 app 对象
        // 源码位置上方[2]
        // -> ensureRenderer方法调用了来自runtime-core的createRenderer
        // 源码位置上方[3]
        // -> createRenderer(HostNode, HostElement),两个通用参数HostNode(主机环境中的节点)和HostElement(宿主环境中的元素),对应于宿主环境。
        // -> reateRenderer(使用(可选的)选项创建一个 Renderer 实例。),该方法返回了 baseCreateRenderer
        // 源码位置上方[4]
        // -> baseCreateRenderer方法最终返回 render hydrate createApp三个函数,生成的 render 传给 createAppAPI ,hydrate 为可选参数,ssr 的场景下会用到;
      const app = ensureRenderer().createApp(...args)
    
      if (__DEV__) {
         // DEV环境下,用于组件名称验证是否是原生标签或者svg属性标签
        injectNativeTagCheck(app)
         // DEV环境下,检查CompilerOptions如果有已弃用的属性,显示警告
        injectCompilerOptionsCheck(app)
      }
    
      const { mount } = app
      // 从创建的app对象中解构获取mount,改写mount方法后 返回app实例
      app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
        // container 是真实的 DOM 元素,normalizeContainer方法使用document.querySelector处理传入的<containerOrSelector>参数,如果在DEV环境下元素不存在 或者 元素为影子DOM并且mode状态为closed,则返回相应的警告 
        const container = normalizeContainer(containerOrSelector)
        // 如果不是真实的DOM元素则 return
        if (!container) return
    	
         // 这里的app._component 其实就是全局API的createApp的第一个参数,源码位置在上方[5]
        const component = app._component
        // component不是函数 并且 没有不包含render、template
        if (!isFunction(component) && !component.render && !component.template) {
          // 不安全的情况
          // 原因:可能在dom模板中执行JS表达式。
          // 用户必须确保内dom模板是可信的。如果它是
          // 模板不应该包含任何用户数据。
            
           //  使用 DOM的innerHTML作为component.template 内容
          component.template = container.innerHTML
          // 2.挂载前检查,获得元素属性的集合遍历如果name不是v-cloak状态 并且属性名称包含v-、:、@ ,会给出vue文档链接提示
          if (__COMPAT__ && __DEV__) {
            for (let i = 0; i < container.attributes.length; i++) {
              const attr = container.attributes[i]
              if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
                compatUtils.warnDeprecation(
                  DeprecationTypes.GLOBAL_MOUNT_CONTAINER,
                  null
                )
                break
              }
            }
          }
        }
    
        // 挂载前清除内容
        container.innerHTML = ''
        // 真正的挂载 (元素, 是否复用[此处个人理解,仅供参考],是否为SVG元素)
        const proxy = mount(container, false, container instanceof SVGElement)
        if (container instanceof Element) {
          // 删除元素上的 v-cloak 指令
          container.removeAttribute('v-cloak')
          // 设置data-v-app属性
          container.setAttribute('data-v-app', '')
        }
        return proxy
      }
    
      return app
    }) as CreateAppFunction<Element>
    
    

    h

    官方定义:返回一个”虚拟节点“,通常缩写为 VNode:一个普通对象,其中包含向 Vue 描述它应在页面上渲染哪种节点的信息,包括所有子节点的描述。它的目的是用于手动编写的渲染函数;

    h是什么意思?根据祖师爷的回复,h 的含义如下:

    It comes from the term "hyperscript", which is commonly used in many virtual-dom implementations. "Hyperscript" itself stands for "script that generates HTML structures" because HTML is the acronym for "hyper-text markup language".

    它来自术语“hyperscript”,该术语常用于许多虚拟 dom 实现。“Hyperscript”本身代表“生成 HTML 结构的脚本”,因为 HTML 是“超文本标记语言”的首字母缩写词。

    回复出处:https://github.com/vuejs/babel-plugin-transform-vue-jsx/issues/6

    其实h()函数和createVNode()函数都是创建dom节点,他们的作用是一样的,但是在VUE3中createVNode()函数的功能比h()函数要多且做了性能优化,渲染节点的速度也更快。

    用法

    • 第一个参数: HTML 标签名、组件、异步组件或函数式组件。使用返回 null 的函数将渲染一个注释。此参数是必需的。

    • 第二个参数: 一个对象,与我们将在模板中使用的 attribute、prop、class 和、style和事件相对应。可选。

    • 第三个参数: 子代 VNode,使用 h() 生成,或者使用字符串来获取“文本 VNode”,或带有插槽的对象。可选。

      // 用法示例
      h('div', {}, [
        'Some text comes first.',
        h('h1', 'A headline'),
        h(MyComponent, {
          someProp: 'foobar'
        })
      ])
      

    源码浅析

    GitHub地址:

    // 源码位置见上方[6]
    export function h(type: any, propsOrChildren?: any, children?: any): VNode {
      const l = arguments.length
      // 如果参数是两个
      if (l === 2) {
          // 判断是否是对象,并且不为数组
        if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
          // 所有VNode对象都有一个 __v_isVNode 属性,isVNode 方法也是根据这个属性来判断是否为VNode对象。
          if (isVNode(propsOrChildren)) {
            return createVNode(type, null, [propsOrChildren])
          }
          // 只包含属性不含有子元素  
          return createVNode(type, propsOrChildren)
        } else {
          // 忽略props属性 
          return createVNode(type, null, propsOrChildren)
        }
      } else {
        if (l > 3) {
          // Array.prototype.slice.call(arguments, 2),这句话的意思就是说把调用方法的参数截取出来,可以理解成是让arguments转换成一个数组对象,让arguments具有slice()方法
          children = Array.prototype.slice.call(arguments, 2)
        } else if (l === 3 && isVNode(children)) {
          // 如果参数长度等于3,并且第三个参数为VNode对象
          children = [children]
        }
        // h 函数内部的主要处理逻辑就是根据参数个数和参数类型,执行相应处理操作,但最终都是通过调用 createVNode 函数来创建 VNode 对象
        return createVNode(type, propsOrChildren, children)
      }
    }
    

    defineComponent

    官方定义:defineComponent 只返回传递给它的对象。但是,就类型而言,返回的值有一个合成类型的构造函数,用于手动渲染函数、TSX 和 IDE 工具支持

    definComponent主要是用来帮助Vue在TS下正确推断出setup()组件的参数类型

    引入 defineComponent() 以正确推断 setup() 组件的参数类型;

    defineComponent 可以正确适配无 props、数组 props 等形式;

    用法

    • 参数:具有组件选项的对象或者是一个 setup 函数,函数名称将作为组件名称来使用

      // 之前写Ts + vue,需要声明相关的数据类型。如下
      // 声明props和return的数据类型
      interface Data {
        [key: string]: unknown
      }
      // 使用的时候入参要加上声明,return也要加上声明
      export default {
        setup(props: Data): Data {
          // ...
          return {
            // ...
          }
        }
      }
      // 非常的繁琐,使用defineComponent 之后,就可以省略这些类型定义,defineComponent 可以接受显式的自定义props接口或从属性验证对象中自动推断;
      
      // 用法示例1:
      import { defineComponent } from 'vue'
      
      const MyComponent = defineComponent({
        data() {
          return { count: 1 }
        },
        methods: {
          increment() {
            this.count++
          }
        }
      })
      
      // 用法示例2:
      // 不只适用于 setup,只要是 Vue 本身的 API ,defineComponent 都可以自动帮你推导。
      import { defineComponent } from 'vue'
      export default defineComponent({
        setup (props, context) {
          // ...
          
          return {
            // ...
          }
        }
      })
      

    源码浅析

    GitHub地址:源码文件位置

    ...
    ...
    ...
    //  实际上这个 api 只是直接 return 传进来的 options,export default defineComponent({}) 是有点等价于export default {},目前看来这样做的最大作用只是限制 type, setup 必须是函数,props 必须是 undefined 或者 对象。
    export function defineComponent(options: unknown) {
      return isFunction(options) ? { setup: options, name: options.name } : options
    }
    

    defineAsyncComponent

    官方定义:创建一个只有在需要时才会加载的异步组件。

    用法

    参数:接受一个返回 Promise 的工厂函数。Promise 的 resolve 回调应该在服务端返回组件定义后被调用。

    // 在 Vue 2.x 中,声明一个异步组件只需这样
    const asyncModal = () => import('./Modal.vue')
    // 或者
    const asyncModal = {
      component: () => import('./Modal.vue'),
      delay: 200,
      timeout: 3000,
      error: ErrorComponent,
      loading: LoadingComponent
    }
    
    
    // 现在,在 Vue 3 中,由于函数式组件被定义为纯函数,因此异步组件的定义需要通过将其包裹在新的 defineAsyncComponent 助手方法中来显式地定义:
    import { defineAsyncComponent } from 'vue'
    import ErrorComponent from './components/ErrorComponent.vue'
    import LoadingComponent from './components/LoadingComponent.vue'
    
    // 不带选项的异步组件
    const asyncModal = defineAsyncComponent(() => import('./Modal.vue'))
    
    // 带选项的异步组件,对 2.x 所做的另一个更改是,component 选项现在被重命名为loader,以便准确地传达不能直接提供组件定义的信息。注意: defineAsyncComponent不能使用在Vue Router上!
    const asyncModalWithOptions = defineAsyncComponent({
      loader: () => import('./Modal.vue'),
      delay: 200,
      timeout: 3000,
      errorComponent: ErrorComponent,
      loadingComponent: LoadingComponent
    })
    

    源码浅析

    GitHub地址: 41行- 196行

    // 源码位置见上方
    export function defineAsyncComponent<
      T extends Component = { new (): ComponentPublicInstance }
    >(source: AsyncComponentLoader<T> | AsyncComponentOptions<T>): T {
          
      if (isFunction(source)) {
        source = { loader: source }
      }
     // 异步组件的参数
      const {
        loader,
        loadingComponent,
        errorComponent,
        delay = 200,
        timeout, // undefined = never times out
        suspensible = true,
        onError: userOnError
      } = source
    
      let pendingRequest: Promise<ConcreteComponent> | null = null
      let resolvedComp: ConcreteComponent | undefined
    
      let retries = 0
      // 重新尝试load得到组件内容
      const retry = () => {
        retries++
        pendingRequest = null
        return load()
      }
    
      const load = (): Promise<ConcreteComponent> => {
        let thisRequest: Promise<ConcreteComponent>
        return (
          // 如果pendingRequest 存在就return,否则实行loader()
          pendingRequest ||
          (thisRequest = pendingRequest = loader()
           // 失败场景处理
            .catch(err => {
              err = err instanceof Error ? err : new Error(String(err))
              if (userOnError) {
                // 对应文档中的 失败捕获回调函数 用户使用
                return new Promise((resolve, reject) => {
                  const userRetry = () => resolve(retry())
                  const userFail = () => reject(err)
                  userOnError(err, userRetry, userFail, retries + 1)
                })
              } else {
                throw err
              }
            })
            .then((comp: any) => {
              // 个人理解:在thisRequest = pendingRequest = loader(),loader()最开始属于等待状态,赋值给pendingRequest、在thisRequest此刻他们是相等的等待状态,当进入then的时候pendingRequest已经发生了改变,所以返回pendingRequest
              if (thisRequest !== pendingRequest && pendingRequest) {
                return pendingRequest
              }
              // 如果在DEV环境则警告
              if (__DEV__ && !comp) {
                warn(
                  `Async component loader resolved to undefined. ` +
                    `If you are using retry(), make sure to return its return value.`
                )
              }
              // interop module default
              if (
                comp &&
                (comp.__esModule || comp[Symbol.toStringTag] === 'Module')
              ) {
                comp = comp.default
              }
              // 如果在DEV环境则警告
              if (__DEV__ && comp && !isObject(comp) && !isFunction(comp)) {
                throw new Error(`Invalid async component load result: ${comp}`)
              }
              resolvedComp = comp
              return comp
            }))
        )
      }
    
      return defineComponent({
        __asyncLoader: load,
        // 异步组件统一名字
        name: 'AsyncComponentWrapper',
        // 组件有setup方法的走setup逻辑
        setup() {
          const instance = currentInstance!
    
          // already resolved
          if (resolvedComp) {
            return () => createInnerComp(resolvedComp!, instance)
          }
    
          const onError = (err: Error) => {
            pendingRequest = null
            handleError(
              err,
              instance,
              ErrorCodes.ASYNC_COMPONENT_LOADER,
              !errorComponent /* do not throw in dev if user provided error component */
            )
          }
    
          // suspense-controlled or SSR.
          // 对应文档中如果父组件是一个 suspense 那么只返回promise结果 其余的控制交给 suspense 处理即可
          if (
            (__FEATURE_SUSPENSE__ && suspensible && instance.suspense) ||
            (__NODE_JS__ && isInSSRComponentSetup)
          ) {
            return load()
              .then(comp => {
                return () => createInnerComp(comp, instance)
              })
              .catch(err => {
                onError(err)
                return () =>
                  errorComponent
                    ? createVNode(errorComponent as ConcreteComponent, {
                        error: err
                      })
                    : null
              })
          }
    
          const loaded = ref(false)
          const error = ref()
          const delayed = ref(!!delay)
    
          if (delay) {
            setTimeout(() => {
              delayed.value = false
            }, delay)
          }
    
          if (timeout != null) {
            setTimeout(() => {
              if (!loaded.value && !error.value) {
                const err = new Error(
                  `Async component timed out after ${timeout}ms.`
                )
                onError(err)
                error.value = err
              }
            }, timeout)
          }
    
          load()
            .then(() => {
              // promise成功返回后触发trigger导致组件更新 重新渲染组件 只不过此时我们已经得到组件内容
              loaded.value = true
            })
            .catch(err => {
              onError(err)
              error.value = err
            })
    
          // 返回的函数会被当做组件实例的 render 函数
          return () => {
            // render初始执行触发 loaded的依赖收集 
            if (loaded.value && resolvedComp) {
              return createInnerComp(resolvedComp, instance)
            } else if (error.value && errorComponent) {
              return createVNode(errorComponent as ConcreteComponent, {
                error: error.value
              })
            } else if (loadingComponent && !delayed.value) {
              return createVNode(loadingComponent as ConcreteComponent)
            }
          }
        }
      }) as any
    }
    
    

    resolveComponent

    官方定义:如果在当前应用实例中可用,则允许按名称解析 component,返回一个 Component。如果没有找到,则返回接收的参数 name

    用法

    参数:已加载的组件的名称

    const app = createApp({})
    app.component('MyComponent', {
      /* ... */
    })
    
    import { resolveComponent } from 'vue'
    render() {
      const MyComponent = resolveComponent('MyComponent')
    }
    

    源码浅析

    GitHub地址:

    // 接收一个name参数,主要还是在resolveAsset方法中做了处理,源码位置见上方[7]
    export function resolveComponent(
      name: string,
      maybeSelfReference?: boolean
    ): ConcreteComponent | string {
      return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name
    }
    
    // resolveAsset源码在上方地址[8]
    function resolveAsset(
      type: AssetTypes,
      name: string,
      warnMissing = true,
      maybeSelfReference = false
    ) {
      // 寻找当前渲染实例,不存在则为当前实例
      const instance = currentRenderingInstance || currentInstance
      if (instance) {
        const Component = instance.type
    
        // 自我名称具有最高的优先级
        if (type === COMPONENTS) {
          // getComponentName 首先判断传入的Component参数是不是函数,如果是函数优先使用.displayName属性,其次使用.name
          const selfName = getComponentName(Component)
          if (
            // camelize 使用replace方法,正则/-(w)/gname,匹配后toUpperCase() 转换成大写
            // capitalize函数:str.charAt(0).toUpperCase() + str.slice(1) 首字母大写 + 处理后的字符
            selfName &&
            (selfName === name ||
              selfName === camelize(name) ||
              selfName === capitalize(camelize(name)))
          ) {
            return Component
          }
        }
    
        const res =
          // 注册
          // 首先检查实例[type],它被解析为选项API
          resolve(instance[type] || (Component as ComponentOptions)[type], name) ||
          // 全局注册
          resolve(instance.appContext[type], name)
    
        if (!res && maybeSelfReference) {
          return Component
        }
    
        if (__DEV__ && warnMissing && !res) {
          warn(`Failed to resolve ${type.slice(0, -1)}: ${name}`)
        }
    
        return res
      } else if (__DEV__) {
        // 如果实例不存在,并且在DEV环境警告:can only be used in render() or setup()
        warn(
          `resolve${capitalize(type.slice(0, -1))} ` +
            `can only be used in render() or setup().`
        )
      }
    }
    

    resolveDynamicComponent

    官方定义: 返回已解析的 Component 或新创建的 VNode,其中组件名称作为节点标签。如果找不到 Component,将发出警告。

    用法

    参数:接受一个参数:component

    import { resolveDynamicComponent } from 'vue'
    render () {
      const MyComponent = resolveDynamicComponent('MyComponent')
    }
    

    源码浅析

    GitHub地址:

    // 源码位置位于上方[9]位置处
    // 根据该函数的名称,我们可以知道它用于解析动态组件,在 resolveDynamicComponent 函数内部,若 component 参数是字符串类型,则会调用前面介绍的 resolveAsset 方法来解析组件,
    // 如果 resolveAsset 函数获取不到对应的组件,则会返回当前 component 参数的值。比如 resolveDynamicComponent('div') 将返回 'div' 字符串
    // 源码见上方[1]地址
    export function resolveDynamicComponent(component: unknown): VNodeTypes {
      if (isString(component)) {
        return resolveAsset(COMPONENTS, component, false) || component
      } else {
        // 无效类型将引发警告,如果 component 参数非字符串类型,则会返回 component || NULL_DYNAMIC_COMPONENT 这行语句的执行结果,其中 NULL_DYNAMIC_COMPONENT 的值是一个 Symbol 对象。
        return (component || NULL_DYNAMIC_COMPONENT) as any
      }
    }
    
    //  resolveAsset函数解析见上方[8]位置处
    

    resolveDirective

    如果在当前应用实例中可用,则允许通过其名称解析一个 directive。返回一个 Directive。如果没有找到,则返回 undefined

    用法

    • 第一个参数:已加载的指令的名称。

    源码浅析

    GitHub地址:

    /**
     * 源码位置见上方[10]位置处
     */
    export function resolveDirective(name: string): Directive | undefined {
      // 然后调用前面介绍的 resolveAsset 方法来解析组件,resolveAsset函数解析见上方[8]位置处
      return resolveAsset(DIRECTIVES, name)
    }
    

    withDirectives

    官方定义:允许将指令应用于 VNode。返回一个包含应用指令的 VNode。

    用法

    • 第一个参数:一个虚拟节点,通常使用 h() 创建

    • 第二个参数:一个指令数组,每个指令本身都是一个数组,最多可以定义 4 个索引。

    import { withDirectives, resolveDirective } from 'vue'
    const foo = resolveDirective('foo')
    const bar = resolveDirective('bar')
    
    return withDirectives(h('div'), [
      [foo, this.x],
      [bar, this.y]
    ])
    

    源码浅析

    GitHub地址:

    // 源码链接在上方[11]位置处
    export function withDirectives<T extends VNode>(
      vnode: T,
      directives: DirectiveArguments
    ): T {
      // 获取当前实例
      const internalInstance = currentRenderingInstance
      if (internalInstance === null) {
        // 如果在 render 函数外面使用 withDirectives() 则会抛出异常:
        __DEV__ && warn(`withDirectives can only be used inside render functions.`)
        return vnode
      }
      const instance = internalInstance.proxy
      // 在 vnode 上绑定 dirs 属性,并且遍历传入的 directives 数组
      const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = [])
      for (let i = 0; i < directives.length; i++) {
        let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]
        if (isFunction(dir)) {
          dir = {
            mounted: dir,
            updated: dir
          } as ObjectDirective
        }
        bindings.push({
          dir,
          instance,
          value,
          oldValue: void 0,
          arg,
          modifiers
        })
      }
      return vnode
    }
    
    

    createRenderer

    官方定义:createRenderer 函数接受两个泛型参数: HostNodeHostElement,对应于宿主环境中的 Node 和 Element 类型。

    用法

    • 第一个参数:HostNode宿主环境中的节点。
    • 第二个参数:Element宿主环境中的元素。
    // 对于 runtime-dom,HostNode 将是 DOM Node 接口,HostElement 将是 DOM Element 接口。
    // 自定义渲染器可以传入特定于平台的类型,如下所示:
    
    // createRenderer(HostNode, HostElement),两个通用参数HostNode(主机环境中的节点)和HostElement(宿主环境中的元素),对应于宿主环境。
    // reateRenderer(使用(可选的)选项创建一个 Renderer 实例。),该方法返回了 baseCreateRenderer
    export function createRenderer<
      HostNode = RendererNode,
      HostElement = RendererElement
    >(options: RendererOptions<HostNode, HostElement>) {
      return baseCreateRenderer<HostNode, HostElement>(options)
    }
    

    源码解析

    export function createRenderer<
      HostNode = RendererNode,
      HostElement = RendererElement
    >(options: RendererOptions<HostNode, HostElement>) {
      return baseCreateRenderer<HostNode, HostElement>(options)
    }
    
    // baseCreateRenderer这个放2000行的左右的代码量,这里就完整不贴过来了,里面是渲染的核心代码,从平台特性 options 取出相关 API,实现了 patch、处理节点、处理组件、更新组件、安装组件实例等等方法,最终返回了一个renderer对象。
    function baseCreateRenderer(
      options: RendererOptions,
      createHydrationFns?: typeof createHydrationFunctions
    ): any {
      // compile-time feature flags check
      if (__ESM_BUNDLER__ && !__TEST__) {
        initFeatureFlags()
      }
    
      if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
        const target = getGlobalThis()
        target.__VUE__ = true
        setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__)
      }
    
      const {
        insert: hostInsert,
        remove: hostRemove,
        patchProp: hostPatchProp,
        forcePatchProp: hostForcePatchProp,
        createElement: hostCreateElement,
        createText: hostCreateText,
        createComment: hostCreateComment,
        setText: hostSetText,
        setElementText: hostSetElementText,
        parentNode: hostParentNode,
        nextSibling: hostNextSibling,
        setScopeId: hostSetScopeId = NOOP,
        cloneNode: hostCloneNode,
        insertStaticContent: hostInsertStaticContent
      } = options
    	...
    	...
        ...
      // 返回 render hydrate createApp三个函数,生成的 render 传给 createAppAPI ,hydrate 为可选参数,ssr 的场景下会用到;
      return {
        render,
        hydrate,
        createApp: createAppAPI(render, hydrate)
      }
    }
    

    nextTick

    官方定义:将回调推迟到下一个 DOM 更新周期之后执行。在更改了一些数据以等待 DOM 更新后立即使用它。

    import { createApp, nextTick } from 'vue'
    
    const app = createApp({
      setup() {
        const message = ref('Hello!')
        const changeMessage = async newMessage => {
          message.value = newMessage
          await nextTick()
          console.log('Now DOM is updated')
        }
      }
    })
    

    源码浅析

    GitHub地址:

    // 源码位置在上方
    
    // 这里直接创建一个异步任务,但是改变dom属性也是异步策略,怎么保证dom加载完成
    // Vue2.x是 会判断浏览器是否支持promise属性 -> 是否支持MutationObserver -> 是否支持setImmediate  -> 都不支持使用setTimeout,Vue3不再支持IE11,所以nextTick直接使用Promise
    
    // Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。
    
    export function nextTick(
      this: ComponentPublicInstance | void,
      fn?: () => void
    ): Promise<void> {
      const p = currentFlushPromise || resolvedPromise
      return fn ? p.then(this ? fn.bind(this) : fn) : p
    }
    
    // 你设置vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。如果你想在 DOM 状态更新后做点什 ,可以在数据变化之后立即使用Vue.nextTick(callback) 。
    

    mergeProps

    官方定义: 将包含 VNode prop 的多个对象合并为一个单独的对象。其返回的是一个新创建的对象,而作为参数传递的对象则不会被修改。

    用法

    参数: 可以传递不限数量的对象

    import { h, mergeProps } from 'vue'
    export default {
      inheritAttrs: false,
      render() {
        const props = mergeProps({
          // 该 class 将与 $attrs 中的其他 class 合并。
          class: 'active'
        }, this.$attrs)
        return h('div', props)
      }
    }
    

    源码浅析

    GitHub地址:

    export function mergeProps(...args: (Data & VNodeProps)[]) {
      // extend就是Object.assign方法, ret合并第一个参数为对象
      const ret = extend({}, args[0])
      // 遍历args参数
      for (let i = 1; i < args.length; i++) {
        const toMerge = args[i]
        for (const key in toMerge) {
          if (key === 'class') {
            // 合并class
            if (ret.class !== toMerge.class) {
              ret.class = normalizeClass([ret.class, toMerge.class])
            }
          } else if (key === 'style') {
            // 合并style
            ret.style = normalizeStyle([ret.style, toMerge.style])
          } else if (isOn(key)) {、
          	// 判断是不是以 on开头的
            const existing = ret[key]
            const incoming = toMerge[key]
            if (existing !== incoming) {
              // 如果第一个参数中不存在,则合并,否则新增
              ret[key] = existing
                ? [].concat(existing as any, incoming as any)
                : incoming
            }
          } else if (key !== '') {
            // key不为空则添加属性
            ret[key] = toMerge[key]
          }
        }
      }
      return ret
    }
    

    useCssModule

    官方定义:允许在 setup单文件组件函数中访问 CSS 模块。

    用法

    • 参数:CSS 模块的名称。默认为 '$style'
    // useCssModule 只能在 render 或 setup 函数中使用。
    // 这里的name不止可以填写$style,
    /*
    *<style module="aaa"
    * ...
    *</style>
    */
    // 这样就可以使用 const style = useCssModule(‘aaa'),来获取相应内容
    
    <script>
    import { h, useCssModule } from 'vue'
    export default {
      setup () {
        const style = useCssModule()
        return () => h('div', {
          class: style.success
        }, 'Task complete!')
      }
    }
    </script>
    <style module>
    .success {
      color: #090;
    }
    </style>
    
    // 在 <style> 上添加 module 后, $style的计算属性就会被自动注入组件。
    <style module>
    .six
     color: red;
    }
    .one
     font-size:62px;
    }
    </style>
    // 添加model后可以直接使用$style绑定属性
    <template>
     <div>
      <p :class="$style.red">
       hello red!
      </p>
     </div>
    </template>
    

    源码解析

    GitHub地址:

    useCssModule()1行 - 30行

    import { warn, getCurrentInstance } from '@vue/runtime-core'
    import { EMPTY_OBJ } from '@vue/shared'
    
    // 取出 this.$style 
    export function useCssModule(name = '$style'): Record<string, string> {
      /* 如果是istanbul覆盖率测试则跳出 */
      if (!__GLOBAL__) {
        // 获取当前实例
        const instance = getCurrentInstance()!
        if (!instance) {
          // useCssModule 只能在 render 或 setup 函数中使用。
          __DEV__ && warn(`useCssModule must be called inside setup()`)
          // EMPTY_OBJ是使用Object.freeze()冻结对象
          return EMPTY_OBJ
        }
        const modules = instance.type.__cssModules
        // 如果不存在css模块,警告
        if (!modules) {
          __DEV__ && warn(`Current instance does not have CSS modules injected.`)
          return EMPTY_OBJ
        }
        const mod = modules[name]
        // 如果不存在未找到name的css模块,警告
        if (!mod) {
          __DEV__ &&
            warn(`Current instance does not have CSS module named "${name}".`)
          return EMPTY_OBJ
        }
        return mod as Record<string, string>
      } else {
        if (__DEV__) {
          warn(`useCssModule() is not supported in the global build.`)
        }
        return EMPTY_OBJ
      }
    }
    

    version

    官方定义: 以字符串形式提供已安装的 Vue 的版本号。

    // vue-next/packages/vue/package.json 中的version 为3.1.2,使用.split('.')[0],得出3
    const version = Number(Vue.version.split('.')[0])
    if (version === 3) {
      // Vue 3
    } else if (version === 2) {
      // Vue 2
    } else {
      // 不支持的 Vue 的版本
    }
    

    参考资料

    Vue-next-GitHub

    Vue3官方文档

    Vue3源码分析

    vue3 VNode

    结尾

    好了,以上就是本篇全部文章内容啦。

    如果遇到问题或者有其他意见可以在下方评论区贴出!

    码字不易。如果觉得本篇文章对你有帮助的话,希望你可以留言点赞支持一下,非常感谢~

  • 相关阅读:
    0309. Best Time to Buy and Sell Stock with Cooldown (M)
    0621. Task Scheduler (M)
    0106. Construct Binary Tree from Inorder and Postorder Traversal (M)
    0258. Add Digits (E)
    0154. Find Minimum in Rotated Sorted Array II (H)
    0797. All Paths From Source to Target (M)
    0260. Single Number III (M)
    0072. Edit Distance (H)
    0103. Binary Tree Zigzag Level Order Traversal (M)
    0312. Burst Balloons (H)
  • 原文地址:https://www.cnblogs.com/zhaohongcheng/p/14953047.html
Copyright © 2020-2023  润新知