Анализ исходного кода — 13 глобальных API в Vue3

внешний интерфейс Vue.js
Анализ исходного кода — 13 глобальных API в Vue3

В этой статье всего 5314 слов, а расчетное время чтения составляет 5-15 минут.

предисловие

бессознательноVue-nextВерсия Vue3 пришла к 3.1.2.Недавно я изучил глобальный Api Vue3 по исходному коду и разобрался во время обучения.Я надеюсь добиться прогресса со всеми.

Давайте рассмотрим их в трех измерениях: официальное определение, использование и анализ исходного кода.

Нижеследующее касается содержания глобального API Vue3.Если у вас есть лучшее понимание и идеи, вы можете оставить сообщение в области комментариев, и я отвечу каждому~

Глобальный API

Глобальный API находится непосредственно вVueМетод монтирования выше, вVue, Глобальный API всего 13. Они есть:

  • createappВозвращает экземпляр приложения, предоставляющий контекст приложения;
  • hвернуть «виртуальный узел;
  • definecomponentВозвращает объект опций, в TS компоненту будет предоставлено правильное определение типа параметра;
  • defineasynccomponentСоздайте асинхронный компонент, который будет загружаться только при необходимости;
  • resolvecomponentРазрешить компонент по имени входящего компонента;
  • resolvedynamiccomponentВозвращает разрешенный Компонент или вновь созданный VNode;
  • resolvedirectiveразрешить директиву по ее имени;
  • withdirectivesвозвращает VNode, содержащий инструкции приложения;
  • createrendererКроссплатформенный пользовательский рендеринг;
  • nexttickЯвляется задержкой вызова функции обратного вызова после следующего обновления данных dom;
  • mergepropsОбъедините несколько объектов, содержащих реквизиты VNode, в один объект;
  • usecssmoduleдоступ к модулям CSS;
  • versionПосмотреть номер версии установленного Vue;

createApp

Официальное определение: возвращает экземпляр приложения, предоставляющий контекст приложения. Все дерево компонентов, смонтированное экземпляром приложения, использует один и тот же контекст.

Как следует из названия, CreateApp действует как функция запуска vue, возвращая экземпляр приложения, каждое приложение Vue сначала создает новый, используя следующую функциюэкземпляр приложения, большинство методов, предоставляемых экземпляром приложения, возвращают один и тот же экземпляр и могут быть объединены в цепочку. Например:

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

Применение

  • первый параметр: получает параметр корневого компонента

  • второй параметр: передать корневую поддержку приложению

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

Анализ исходного кода

Адрес гитхаба:

// 源码位置上方[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 следующее:

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".

Оно происходит от термина «гиперскрипт», который обычно используется во многих реализациях виртуального дома. Сам «гиперскрипт» означает «сценарий, который генерирует структуру HTML», потому что HTML является аббревиатурой от «языка гипертекстовой разметки».

Ответ от:GitHub.com/vUEJS/Вавилон…

Фактически, функция h() и функция createVNode() создают узлы dom, и их функции одинаковы, но в VUE3 функция createVNode() имеет больше функций, чем функция h(), и оптимизирована для повышения производительности. , рендеринг скорости узла. Также быстрее.

Применение

  • Первый параметр:Имя тега HTML, компонент, асинхронный компонент или функциональный компонент. Использование функции, возвращающей null, отобразит аннотацию. Этот параметр является обязательным.

  • Второй параметр:Объект, соответствующий атрибутам, пропсам, классам, стилям и событиям, которые мы будем использовать в шаблоне. Необязательный.

  • Третий параметр:Дочерний VNode, используйтеh()Сгенерируйте или используйте строку, чтобы получить «текстовый VNode» или объект со слотом. Необязательный.

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

Анализ исходного кода

Адрес гитхаба:

// 源码位置见上方[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 правильно определить тип параметра компонента setup() в TS.

Ввести функцию defineComponent(), чтобы правильно определять типы параметров для компонентов setup();

defineComponent может корректно адаптироваться к форме без реквизита, реквизита массива и т.д.;

Применение

  • **параметры:** объект с параметрами компонента или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 {
          // ...
        }
      }
    })
    

Анализ исходного кода

Адрес гитхаба:Расположение исходного файла

...
...
...
//  实际上这个 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заводская функция. Обещания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
})

Анализ исходного кода

Адрес гитхаба:Строка 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')
}

Анализ исходного кода

Адрес гитхаба:

// 接收一个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')
}

Анализ исходного кода

Адрес гитхаба:

// 源码位置位于上方[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.

Применение

  • Первый параметр: имя загруженной директивы.

Анализ исходного кода

Адрес гитхаба:

/**
 * 源码位置见上方[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]
])

Анализ исходного кода

Адрес гитхаба:

// 源码链接在上方[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 принимает два общих параметра:HostNodeа такжеHostElement, соответствующие типам узла и элемента в среде размещения.

Применение

  • Первый параметр: узел в среде хостинга 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')
    }
  }
})

Анализ исходного кода

Адрес гитхаба:

// 源码位置在上方

// 这里直接创建一个异步任务,但是改变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, в один объект. Он возвращает вновь созданный объект, а объект, переданный в качестве параметра, не изменяется.

Применение

Параметры: Можно передавать неограниченное количество объектов

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

Анализ исходного кода

Адрес гитхаба:

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>

Анализ исходного кода

Адрес гитхаба:

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

конец

Ну, это все для этой статьи.

Если у вас есть какие-либо вопросы или другие комментарии, пожалуйста, оставьте их в разделе комментариев ниже!

Кодить не просто. Если вы считаете, что эта статья полезна для вас, я надеюсь, что вы можете оставить сообщение, чтобы поставить лайк и поддержать, большое спасибо~