<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          源碼淺析-Vue3中的13個全局Api

          共 46833字,需瀏覽 94分鐘

           ·

          2021-08-02 15:33

          點擊上方 前端Q,關注公眾號

          回復加群,加入前端Q技術交流群

          本文章共5314字,預計閱讀時間5-15分鐘。

          前言

          不知不覺Vue-next[1]的版本已經(jīng)來到了3.1.2,最近對照著源碼學習Vue3的全局Api,邊學習邊整理了下來,希望可以和大家一起進步。

          我們以官方定義、用法、源碼淺析三個維度來一起看看它們。

          下文是關于Vue3全局Api的內(nèi)容,大家如果有更好的理解和想法,可以在評論區(qū)留言,每條我都會回復~

          全局API

          全局API是直接在Vue上掛載方法,在Vue中,全局API一共有13個。分別是:

          • createapp 返回一個提供應用上下文的應用實例;
          • h 返回一個”虛擬節(jié)點;
          • definecomponent 返回options的對象,在TS下,會給予組件正確的參數(shù)類型推斷;
          • defineasynccomponent 創(chuàng)建一個只有在需要時才會加載的異步組件;
          • resolvecomponent 按傳入的組件名稱解析 component;
          • resolvedynamiccomponent 返回已解析的Component或新建的VNode;
          • resolvedirective 通過其名稱解析一個 directive;
          • withdirectives 返回一個包含應用指令的 VNode;
          • createrenderer 跨平臺自定義渲染;
          • nexttick 是將回調(diào)函數(shù)延遲在下一次dom更新數(shù)據(jù)后調(diào)用;
          • mergeprops 將包含 VNode prop 的多個對象合并為一個單獨的對象;
          • usecssmodule 訪問 CSS 模塊;
          • version 查看已安裝的 Vue 的版本號;

          createApp

          官方定義:返回一個提供應用上下文的應用實例。應用實例掛載的整個組件樹共享同一個上下文。

          顧名思義,CreateApp 作為 vue 的啟動函數(shù),返回一個應用實例,每個 Vue 應用程序都首先使用以下函數(shù)創(chuàng)建一個新的應用程序?qū)嵗?/strong>,應用程序?qū)嵗_的大多數(shù)方法都返回相同的實例,可以鏈式調(diào)用。例如:

          Vue.createApp({}).component('SearchInput', SearchInputComponent)
          復制代碼

          用法

          • 第一個參數(shù): 接收一個根組件選項

            • 第二個參數(shù): 將根 prop 傳遞給應用程序
          // 用法示例
          import { createApp, h, nextTick } from 'vue'
          const app = createApp({
            data() {
              return {
                ...
              }
            },
            methods: {...},
            computed: {...}
            ...
          },
              { username'Evan' })
          復制代碼

          源碼淺析

          GitHub地址:

          • createApp()56行 - 102行內(nèi)容 \[1\][2]
          • ensureRenderer()35 行- 37行內(nèi)容 \[2\][3]
          • createRenderer()419 行- 424行內(nèi)容 \[3\][4]
          • baseCreateRenderer()448 行- 2418行 \[4\][5]
          • app._component:174行\(zhòng)[5\][6]
          // 源碼位置上方[1]
          export const createApp = ((...args) => {
              // 使用ensureRenderer().createApp() 來創(chuàng)建 app 對象
              // 源碼位置上方[2]
              // -> ensureRenderer方法調(diào)用了來自runtime-core的createRenderer
              // 源碼位置上方[3]
              // -> createRenderer(HostNode, HostElement),兩個通用參數(shù)HostNode(主機環(huán)境中的節(jié)點)和HostElement(宿主環(huán)境中的元素),對應于宿主環(huán)境。
              // -> reateRenderer(使用(可選的)選項創(chuàng)建一個 Renderer 實例。),該方法返回了 baseCreateRenderer
              // 源碼位置上方[4]
              // -> baseCreateRenderer方法最終返回 render hydrate createApp三個函數(shù),生成的 render 傳給 createAppAPI ,hydrate 為可選參數(shù),ssr 的場景下會用到;
            const app = ensureRenderer().createApp(...args)

            if (__DEV__) {
               // DEV環(huán)境下,用于組件名稱驗證是否是原生標簽或者svg屬性標簽
              injectNativeTagCheck(app)
               // DEV環(huán)境下,檢查CompilerOptions如果有已棄用的屬性,顯示警告
              injectCompilerOptionsCheck(app)
            }

            const { mount } = app
            // 從創(chuàng)建的app對象中解構(gòu)獲取mount,改寫mount方法后 返回app實例
            app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
              // container 是真實的 DOM 元素,normalizeContainer方法使用document.querySelector處理傳入的<containerOrSelector>參數(shù),如果在DEV環(huán)境下元素不存在 或者 元素為影子DOM并且mode狀態(tài)為closed,則返回相應的警告 
              const container = normalizeContainer(containerOrSelector)
              // 如果不是真實的DOM元素則 return
              if (!container) return
           
               // 這里的app._component 其實就是全局API的createApp的第一個參數(shù),源碼位置在上方[5]
              const component = app._component
              // component不是函數(shù) 并且 沒有不包含render、template
              if (!isFunction(component) && !component.render && !component.template) {
                // 不安全的情況
                // 原因:可能在dom模板中執(zhí)行JS表達式。
                // 用戶必須確保內(nèi)dom模板是可信的。如果它是
                // 模板不應該包含任何用戶數(shù)據(jù)。
                  
                 //  使用 DOM的innerHTML作為component.template 內(nèi)容
                component.template = container.innerHTML
                // 2.掛載前檢查,獲得元素屬性的集合遍歷如果name不是v-cloak狀態(tài) 并且屬性名稱包含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
                    }
                  }
                }
              }

              // 掛載前清除內(nèi)容
              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

          官方定義:返回一個”虛擬節(jié)點“,通常縮寫為 VNode:一個普通對象,其中包含向 Vue 描述它應在頁面上渲染哪種節(jié)點的信息,包括所有子節(jié)點的描述。它的目的是用于手動編寫的渲染函數(shù);

          h是什么意思?根據(jù)祖師爺?shù)幕貜停琱 的含義如下:

          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 實現(xiàn)。“Hyperscript”本身代表“生成 HTML 結(jié)構(gòu)的腳本”,因為 HTML 是“超文本標記語言”的首字母縮寫詞。

          回復出處:github.com/vuejs/babel…[7]

          其實h()函數(shù)和createVNode()函數(shù)都是創(chuàng)建dom節(jié)點,他們的作用是一樣的,但是在VUE3中createVNode()函數(shù)的功能比h()函數(shù)要多且做了性能優(yōu)化,渲染節(jié)點的速度也更快。

          用法

          • 第一個參數(shù): HTML 標簽名、組件、異步組件或函數(shù)式組件。使用返回 null 的函數(shù)將渲染一個注釋。此參數(shù)是必需的。

          • 第二個參數(shù): 一個對象,與我們將在模板中使用的 attribute、prop、class 和、style和事件相對應。可選。

          • 第三個參數(shù): 子代 VNode,使用 h() 生成,或者使用字符串來獲取“文本 VNode”,或帶有插槽的對象。可選。

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

          源碼淺析

          GitHub地址:

          • h:174行 - 196行 \[6\][8]
          // 源碼位置見上方[6]
          export function h(type: any, propsOrChildren?: any, children?: any): VNode {
            const l = arguments.length
            // 如果參數(shù)是兩個
            if (l === 2) {
                // 判斷是否是對象,并且不為數(shù)組
              if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
                // 所有VNode對象都有一個 __v_isVNode 屬性,isVNode 方法也是根據(jù)這個屬性來判斷是否為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),這句話的意思就是說把調(diào)用方法的參數(shù)截取出來,可以理解成是讓arguments轉(zhuǎn)換成一個數(shù)組對象,讓arguments具有slice()方法
                children = Array.prototype.slice.call(arguments2)
              } else if (l === 3 && isVNode(children)) {
                // 如果參數(shù)長度等于3,并且第三個參數(shù)為VNode對象
                children = [children]
              }
              // h 函數(shù)內(nèi)部的主要處理邏輯就是根據(jù)參數(shù)個數(shù)和參數(shù)類型,執(zhí)行相應處理操作,但最終都是通過調(diào)用 createVNode 函數(shù)來創(chuàng)建 VNode 對象
              return createVNode(type, propsOrChildren, children)
            }
          }
          復制代碼

          defineComponent

          官方定義:defineComponent 只返回傳遞給它的對象。但是,就類型而言,返回的值有一個合成類型的構(gòu)造函數(shù),用于手動渲染函數(shù)、TSX 和 IDE 工具支持

          definComponent主要是用來幫助Vue在TS下正確推斷出setup()組件的參數(shù)類型

          引入 defineComponent() 以正確推斷 setup() 組件的參數(shù)類型;

          defineComponent 可以正確適配無 props、數(shù)組 props 等形式;

          用法

          • **參數(shù):**具有組件選項的對象或者是一個 setup 函數(shù),函數(shù)名稱將作為組件名稱來使用

            // 之前寫Ts + vue,需要聲明相關的數(shù)據(jù)類型。如下
            // 聲明props和return的數(shù)據(jù)類型
            interface Data {
            [key: string]: unknown
            }
            // 使用的時候入?yún)⒁由下暶鳎瑀eturn也要加上聲明
            export default {
            setup(props: Data): Data {
              // ...
              return {
                // ...
              }
            }
            }
            // 非常的繁瑣,使用defineComponent 之后,就可以省略這些類型定義,defineComponent 可以接受顯式的自定義props接口或從屬性驗證對象中自動推斷;

            // 用法示例1:
            import { defineComponent } from 'vue'

            const MyComponent = defineComponent({
            data() {
              return { count1 }
            },
            methods: {
              increment() {
                this.count++
              }
            }
            })

            // 用法示例2:
            // 不只適用于 setup,只要是 Vue 本身的 API ,defineComponent 都可以自動幫你推導。
            import { defineComponent } from 'vue'
            export default defineComponent({
            setup (props, context) {
              // ...
              
              return {
                // ...
              }
            }
            })
            復制代碼

          源碼淺析

          GitHub地址:源碼文件位置[9]

          ...
          ...
          ...
          //  實際上這個 api 只是直接 return 傳進來的 options,export default defineComponent({}) 是有點等價于export default {},目前看來這樣做的最大作用只是限制 type, setup 必須是函數(shù),props 必須是 undefined 或者 對象。
          export function defineComponent(options: unknown{
            return isFunction(options) ? { setup: options, name: options.name } : options
          }
          復制代碼

          defineAsyncComponent

          官方定義:創(chuàng)建一個只有在需要時才會加載的異步組件。

          用法

          參數(shù):接受一個返回 Promise 的工廠函數(shù)。Promise 的 resolve 回調(diào)應該在服務端返回組件定義后被調(diào)用。

          // 在 Vue 2.x 中,聲明一個異步組件只需這樣
          const asyncModal = () => import('./Modal.vue')
          // 或者
          const asyncModal = {
            component() => import('./Modal.vue'),
            delay200,
            timeout3000,
            error: ErrorComponent,
            loading: LoadingComponent
          }


          // 現(xiàn)在,在 Vue 3 中,由于函數(shù)式組件被定義為純函數(shù),因此異步組件的定義需要通過將其包裹在新的 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 選項現(xiàn)在被重命名為loader,以便準確地傳達不能直接提供組件定義的信息。注意:defineAsyncComponent不能使用在Vue Router上!
          const asyncModalWithOptions = defineAsyncComponent({
            loader() => import('./Modal.vue'),
            delay200,
            timeout3000,
            errorComponent: ErrorComponent,
            loadingComponent: LoadingComponent
          })
          復制代碼

          源碼淺析

          GitHub地址:41行- 196行[10]

          // 源碼位置見上方
          export function defineAsyncComponent<
            T extends Component = 
          new (): ComponentPublicInstance }
          >(source: AsyncComponentLoader<T> | AsyncComponentOptions<T>): T {
                
            if (isFunction(source)) {
              source = { loader: source }
            }
           // 異步組件的參數(shù)
            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得到組件內(nèi)容
            const retry = () => {
              retries++
              pendingRequest = null
              return load()
            }

            const load = (): Promise<ConcreteComponent> => {
              let thisRequest: Promise<ConcreteComponent>
              return (
                // 如果pendingRequest 存在就return,否則實行l(wèi)oader()
                pendingRequest ||
                (thisRequest = pendingRequest = loader()
                 // 失敗場景處理
                  .catch(err => {
                    err = err instanceof Error ? err : new Error(String(err))
                    if (userOnError) {
                      // 對應文檔中的 失敗捕獲回調(diào)函數(shù) 用戶使用
                      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()最開始屬于等待狀態(tài),賦值給pendingRequest、在thisRequest此刻他們是相等的等待狀態(tài),當進入then的時候pendingRequest已經(jīng)發(fā)生了改變,所以返回pendingRequest
                    if (thisRequest !== pendingRequest && pendingRequest) {
                      return pendingRequest
                    }
                    // 如果在DEV環(huán)境則警告
                    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環(huán)境則警告
                    if (__DEV__ && comp && !isObject(comp) && !isFunction(comp)) {
                      throw new Error(`Invalid async component load result: ${comp}`)
                    }
                    resolvedComp = comp
                    return comp
                  }))
              )
            }

            return defineComponent({
              __asyncLoader: load,
              // 異步組件統(tǒng)一名字
              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結(jié)果 其余的控制交給 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成功返回后觸發(fā)trigger導致組件更新 重新渲染組件 只不過此時我們已經(jīng)得到組件內(nèi)容
                    loaded.value = true
                  })
                  .catch(err => {
                    onError(err)
                    error.value = err
                  })

                // 返回的函數(shù)會被當做組件實例的 render 函數(shù)
                return () => {
                  // render初始執(zhí)行觸發(fā) 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。如果沒有找到,則返回接收的參數(shù) name

          用法

          參數(shù):已加載的組件的名稱

          const app = createApp({})
          app.component('MyComponent', {
            /* ... */
          })

          import { resolveComponent } from 'vue'
          render() {
            const MyComponent = resolveComponent('MyComponent')
          }
          復制代碼

          源碼淺析

          GitHub地址:

          • resolveComponent():21行- 27行 \[7\][11]
          • resolveAsset():62行- 123行 \[8\][12]
          // 接收一個name參數(shù),主要還是在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

              // 自我名稱具有最高的優(yōu)先級
              if (type === COMPONENTS) {
                // getComponentName 首先判斷傳入的Component參數(shù)是不是函數(shù),如果是函數(shù)優(yōu)先使用.displayName屬性,其次使用.name
                const selfName = getComponentName(Component)
                if (
                  // camelize 使用replace方法,正則/-(\w)/gname,匹配后toUpperCase() 轉(zhuǎn)換成大寫
                  // capitalize函數(shù):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環(huán)境警告: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 或新創(chuàng)建的 VNode,其中組件名稱作為節(jié)點標簽。如果找不到 Component,將發(fā)出警告。

          用法

          參數(shù):接受一個參數(shù):component

          import { resolveDynamicComponent } from 'vue'
          render () {
            const MyComponent = resolveDynamicComponent('MyComponent')
          }
          復制代碼

          源碼淺析

          GitHub地址:

          • resolveDirective()43行 - 48行內(nèi)容 \[9\][13]
          • resolveAsset():62行- 123行[14]
          // 源碼位置位于上方[9]位置處
          // 根據(jù)該函數(shù)的名稱,我們可以知道它用于解析動態(tài)組件,在 resolveDynamicComponent 函數(shù)內(nèi)部,若 component 參數(shù)是字符串類型,則會調(diào)用前面介紹的 resolveAsset 方法來解析組件,
          // 如果 resolveAsset 函數(shù)獲取不到對應的組件,則會返回當前 component 參數(shù)的值。比如 resolveDynamicComponent('div') 將返回 'div' 字符串
          // 源碼見上方[1]地址
          export function resolveDynamicComponent(component: unknown): VNodeTypes {
            if (isString(component)) {
              return resolveAsset(COMPONENTS, component, false) || component
            } else {
              // 無效類型將引發(fā)警告,如果 component 參數(shù)非字符串類型,則會返回 component || NULL_DYNAMIC_COMPONENT 這行語句的執(zhí)行結(jié)果,其中 NULL_DYNAMIC_COMPONENT 的值是一個 Symbol 對象。
              return (component || NULL_DYNAMIC_COMPONENT) as any
            }
          }

          //  resolveAsset函數(shù)解析見上方[8]位置處
          復制代碼

          resolveDirective

          如果在當前應用實例中可用,則允許通過其名稱解析一個 directive。返回一個 Directive。如果沒有找到,則返回 undefined

          用法

          • 第一個參數(shù):已加載的指令的名稱。

          源碼淺析

          GitHub地址:

          • resolveDirective()43行 - 48行內(nèi)容 \[10\][15]
          • resolveAsset():62行- 123行[16]
          /**
           * 源碼位置見上方[10]位置處
           */

          export function resolveDirective(name: string): Directive | undefined {
            // 然后調(diào)用前面介紹的 resolveAsset 方法來解析組件,resolveAsset函數(shù)解析見上方[8]位置處
            return resolveAsset(DIRECTIVES, name)
          }
          復制代碼

          withDirectives

          官方定義:允許將指令應用于 VNode。返回一個包含應用指令的 VNode。

          用法

          • 第一個參數(shù):一個虛擬節(jié)點,通常使用 h() 創(chuàng)建

            • 第二個參數(shù):一個指令數(shù)組,每個指令本身都是一個數(shù)組,最多可以定義 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地址:

          • resolveDirective()85行 - 114內(nèi)容 \[11\][17]
          // 源碼鏈接在上方[11]位置處
          export function withDirectives<T extends VNode>(
            vnode: T,
            directives: DirectiveArguments
          ): T 
          {
            // 獲取當前實例
            const internalInstance = currentRenderingInstance
            if (internalInstance === null) {
              // 如果在 render 函數(shù)外面使用 withDirectives() 則會拋出異常:
              __DEV__ && warn(`withDirectives can only be used inside render functions.`)
              return vnode
            }
            const instance = internalInstance.proxy
            // 在 vnode 上綁定 dirs 屬性,并且遍歷傳入的 directives 數(shù)組
            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,
                oldValuevoid 0,
                arg,
                modifiers
              })
            }
            return vnode
          }

          復制代碼

          createRenderer

          官方定義:createRenderer 函數(shù)接受兩個泛型參數(shù):HostNodeHostElement,對應于宿主環(huán)境中的 Node 和 Element 類型。

          用法

          • 第一個參數(shù):HostNode宿主環(huán)境中的節(jié)點。
          • 第二個參數(shù):Element宿主環(huán)境中的元素。
          // 對于 runtime-dom,HostNode 將是 DOM Node 接口,HostElement 將是 DOM Element 接口。
          // 自定義渲染器可以傳入特定于平臺的類型,如下所示:

          // createRenderer(HostNode, HostElement),兩個通用參數(shù)HostNode(主機環(huán)境中的節(jié)點)和HostElement(宿主環(huán)境中的元素),對應于宿主環(huán)境。
          // reateRenderer(使用(可選的)選項創(chuàng)建一個 Renderer 實例。),該方法返回了 baseCreateRenderer
          export function createRenderer<
            HostNode = RendererNode,
            HostElement = RendererElement
          >(options: RendererOptions<HostNode, HostElement>
          {
            return baseCreateRenderer<HostNode, HostElement>(options)
          }
          復制代碼

          源碼解析

          • createRenderer()419 行- 424行內(nèi)容 \[3\][18]
          • baseCreateRenderer()448 行- 2418行 \[4\][19]
          export function createRenderer<
            HostNode = RendererNode,
            HostElement = RendererElement
          >(options: RendererOptions<HostNode, HostElement>
          {
            return baseCreateRenderer<HostNode, HostElement>(options)
          }

          // baseCreateRenderer這個放2000行的左右的代碼量,這里就完整不貼過來了,里面是渲染的核心代碼,從平臺特性 options 取出相關 API,實現(xiàn)了 patch、處理節(jié)點、處理組件、更新組件、安裝組件實例等等方法,最終返回了一個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三個函數(shù),生成的 render 傳給 createAppAPI ,hydrate 為可選參數(shù),ssr 的場景下會用到;
            return {
              render,
              hydrate,
              createApp: createAppAPI(render, hydrate)
            }
          }
          復制代碼

          nextTick

          官方定義:將回調(diào)推遲到下一個 DOM 更新周期之后執(zhí)行。在更改了一些數(shù)據(jù)以等待 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地址:

          • nextTick()42行 - 48行內(nèi)容[20]
          // 源碼位置在上方

          // 這里直接創(chuàng)建一個異步任務,但是改變dom屬性也是異步策略,怎么保證dom加載完成
          // Vue2.x是 會判斷瀏覽器是否支持promise屬性 -> 是否支持MutationObserver -> 是否支持setImmediate  -> 都不支持使用setTimeout,Vue3不再支持IE11,所以nextTick直接使用Promise

          // Vue 異步執(zhí)行 DOM 更新。只要觀察到數(shù)據(jù)變化,Vue 將開啟一個隊列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)改變。如果同一個 watcher 被多次觸發(fā),只會被推入到隊列中一次。這種在緩沖時去除重復數(shù)據(jù)對于避免不必要的計算和 DOM 操作上非常重要。然后,在下一個的事件循環(huán)“tick”中,Vue 刷新隊列并執(zhí)行實際 (已去重的) 工作。

          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',該組件不會立即重新渲染。當刷新隊列時,組件會在事件循環(huán)隊列清空時的下一個“tick”更新。如果你想在 DOM 狀態(tài)更新后做點什 ,可以在數(shù)據(jù)變化之后立即使用Vue.nextTick(callback) 。
          復制代碼

          mergeProps

          官方定義:將包含 VNode prop 的多個對象合并為一個單獨的對象。其返回的是一個新創(chuàng)建的對象,而作為參數(shù)傳遞的對象則不會被修改。

          用法

          參數(shù):可以傳遞不限數(shù)量的對象

          import { h, mergeProps } from 'vue'
          export default {
            inheritAttrsfalse,
            render() {
              const props = mergeProps({
                // 該 class 將與 $attrs 中的其他 class 合并。
                class'active'
              }, this.$attrs)
              return h('div', props)
            }
          }
          復制代碼

          源碼淺析

          GitHub地址:

          • mergeProps()687行 - 712行[21]
          export function mergeProps(...args: (Data & VNodeProps)[]) {
            // extend就是Object.assign方法, ret合并第一個參數(shù)為對象
            const ret = extend({}, args[0])
            // 遍歷args參數(shù)
            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) {
                    // 如果第一個參數(shù)中不存在,則合并,否則新增
                    ret[key] = existing
                      ? [].concat(existing as any, incoming as any)
                      : incoming
                  }
                } else if (key !== '') {
                  // key不為空則添加屬性
                  ret[key] = toMerge[key]
                }
              }
            }
            return ret
          }
          復制代碼

          useCssModule

          官方定義:允許在 `setup`[22]單文件組件[23]函數(shù)中訪問 CSS 模塊。

          用法

          • 參數(shù):CSS 模塊的名稱。默認為 '$style'
          // useCssModule 只能在 render 或 setup 函數(shù)中使用。
          // 這里的name不止可以填寫$style,
          /*
          *<style module="aaa"
          * ...
          *</style>
          */

          // 這樣就可以使用 const style = useCssModule(‘a(chǎn)aa'),來獲取相應內(nèi)容

          <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
           colorred;
          }
          .one
           font-size:62px;
          }
          </style>

          // 添加model后可以直接使用$style綁定屬性
          <template>
           <div>
            <p :class="$style.red">
             hello red!
            </p>
           </div>
          </template>

          復制代碼

          源碼解析

          GitHub地址:

          useCssModule()1行 \- 30行[24]

          import { warn, getCurrentInstance } from '@vue/runtime-core'
          import { EMPTY_OBJ } from '@vue/shared'

          // 取出 this.$style 
          export function useCssModule(name = '$style'): Record<stringstring{
            /* 如果是istanbul覆蓋率測試則跳出 */
            if (!__GLOBAL__) {
              // 獲取當前實例
              const instance = getCurrentInstance()!
              if (!instance) {
                // useCssModule 只能在 render 或 setup 函數(shù)中使用。
                __DEV__ && warn(`useCssModule must be called inside setup()`)
                // EMPTY_OBJ是使用Object.freeze()凍結(jié)對象
                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[25]

          Vue3官方文檔[26]

          Vue3源碼分析[27]

          vue3 VNode[28]

          結(jié)尾

          好了,以上就是本篇全部文章內(nèi)容啦。

          如果遇到問題或者有其他意見可以在下方評論區(qū)貼出!

          碼字不易。如果覺得本篇文章對你有幫助的話,希望你可以留言點贊支持一下,非常感謝~

          關于本文

          來源:Tz

          https://juejin.cn/post/6979394726927532068




          內(nèi)推社群


          我組建了一個氛圍特別好的騰訊內(nèi)推社群,如果你對加入騰訊感興趣的話(后續(xù)有計劃也可以),我們可以一起進行面試相關的答疑、聊聊面試的故事、并且在你準備好的時候隨時幫你內(nèi)推。下方加 winty 好友回復「面試」即可。


          瀏覽 55
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  2021黄片| 啪啪视频免费网站 | 加勒比综合色 | 毛片福利 | 黄片视频在线免费看 |