国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Vue源碼淺析之異步組件注冊

Shonim / 1065人閱讀

Vue的異步組件注冊

Vue官方文檔提供注冊異步組件的方式有三種:

工廠函數執行 resolve 回調

工廠函數中返回Promise

工廠函數返回一個配置化組件對象

工廠函數執行 resolve 回調

我們看下 Vue 官方文檔提供的示例:

Vue.component("async-webpack-example", function (resolve) {
  // 這個特殊的 `require` 語法將會告訴 webpack
  // 自動將你的構建代碼切割成多個包, 這些包
  // 會通過 Ajax 請求加載
  require(["./my-async-component"], resolve)
})

簡單說明一下, 這個示例調用 Vue 的靜態方法 component 實現組件注冊, 需要了解下 Vue.component 的大致實現

// 此時type為component
Vue[type] = function (
  id: string,
  definition: Function | Object
): Function | Object | void {
  if (!definition) {
    return this.options[type + "s"][id]
  } else {
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== "production" && type === "component") {
      validateComponentName(id)
    }
    // 是否為對象
    if (type === "component" && isPlainObject(definition)) {
      definition.name = definition.name || id
      definition = this.options._base.extend(definition)
    }
    if (type === "directive" && typeof definition === "function") {
      definition = { bind: definition, update: definition }
    }
    // 記錄當前Vue的全局components, filters, directives對應的聲明映射
    this.options[type + "s"][id] = definition
    return definition
  }
}

先判斷傳入的 definition 也就是我們的工廠函數, 是否為對象, 都說是工廠函數了, 那肯定不為對象, 于是這里不調用 this.options._base.extend(definition) 來獲取組件的構造函數, 而是直接把當前的 definition(工廠函數) 保存到 this.options.components 的 "async-webpack-example" 屬性值中, 并返回definition。

接下來發生什么事情呢?
其實剛才我們只是調用了 Vue.component 注冊一個異步組件, 但是我們最終是通過 new Vue 實例來實現頁面的渲染。這里大致瀏覽一下渲染的過程:

Run:

new Vue執行構造函數

構造函數 執行 this._init, 在 initMixin 執行的時候定義 Vue.prototype._init

$mount執行, 在 web/runtime/index.js 中已經進行定義 Vue.prototype.$mount

執行 core/instance/lifecycle.js 中的 mountComponent

實例化渲染Watcher, 并傳入 updateComponent(通過 Watcher 實例對象的 getter 觸發vm._update, 而至于怎么觸發先忽略, 會另外講解)

vm._update 觸發 vm._render(renderMixin 時定義在 Vue.prototype._render) 執行

在 vm.$options 中獲取 render 函數并執行, 使得傳入的 vm.$createElement(在 initRender 中定義在vm中)執行, vm.$createElement也就是平時書寫的 h => h(App)這個h函數。

vm.$createElement = createElement

createComponent 通過 resolveAsset 查詢當前組件是否正常注冊

所以我們現在以及進入到 createComponent 這個函數了, 看下這里異步組件具體的實現邏輯:

export function createComponent (
  Ctor: Class | Function | Object | void,
  data: ?VNodeData,
  context: Component, // vm實例
  children: ?Array,
  tag?: string
): VNode | Array | void {

  // 在init初始化的時候賦值Vue
  const baseCtor = context.$options._base

  // Ctor當前為異步組件的工廠函數, 所以此步驟不執行
  if (isObject(Ctor)) {
    // 獲取構造器, 對于非全局注冊的組件使用
    Ctor = baseCtor.extend(Ctor)
  }

  // async component
  let asyncFactory
  // 如果Ctro.cid為undefined, 則說明h會是異步組件注冊
  // 原因是沒有調用過 Vue.extend 進行組件構造函數轉換獲取
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    // 解析異步組件
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
    // Ctor為undefined則直接創建并返回異步組件的占位符組件Vnode
    if (Ctor === undefined) {
      return createAsyncPlaceholder(
        asyncFactory,
        data,
        context,
        children,
        tag
      )
    }
  }

  ...此處省略不分析的代碼

  // 安裝組件的鉤子
  installComponentHooks(data)

  // return a placeholder vnode
  const name = Ctor.options.name || tag
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ""}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children }, // 組件對象componentOptions
    asyncFactory
  )

  return vnode
}

從源碼我們可以看出, 異步組件不執行組件構造器的轉換獲取, 而是執行 resolveAsyncComponent 來獲取返回的組件構造器。由于該過程是異步請求組件, 所以我們看下 resolveAsyncComponent 的實現

// 定義在render.js中的全局變量, 用于記錄當前正在渲染的vm實例
import { currentRenderingInstance } from "core/instance/render"

export function resolveAsyncComponent (
  factory: Function,
  baseCtor: Class
): Class | void {
  // 高級異步組件使用
  if (isTrue(factory.error) && isDef(factory.errorComp)) {...先省略}

  if (isDef(factory.resolved)) {
    return factory.resolved
  }
  // 獲取當前正在渲染的vm實例
  const owner = currentRenderingInstance
  if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
    // already pending
    factory.owners.push(owner)
  }

  if (isTrue(factory.loading) && isDef(factory.loadingComp)) {...省略}

  // 執行該邏輯
  if (owner && !isDef(factory.owners)) {
    const owners = factory.owners = [owner]
    // 用于標記是否
    let sync = true

    ...省略
    const forceRender = (renderCompleted: boolean) => { ...省略 }
    
    // once讓被once包裝的任何函數的其中一個只執行一次
    const resolve = once((res: Object | Class) => {
      // cache resolved
      factory.resolved = ensureCtor(res, baseCtor)
      // invoke callbacks only if this is not a synchronous resolve
      // (async resolves are shimmed as synchronous during SSR)
      if (!sync) {
        forceRender(true)
      } else {
        owners.length = 0
      }
    })

    const reject = once(reason => { ...省略 })

    // 執行工廠函數, 比如webpack獲取異步組件資源
    const res = factory(resolve, reject)
    
    ...省略
    
    sync = false
    // return in case resolved synchronously
    return factory.loading
      ? factory.loadingComp
      : factory.resolved
  }
}

resolveAsyncComponent 傳入異步組件工廠函數和 baseCtor(也就是Vue.extend), 先獲取當前渲染的vm實例接著標記sync為true, 表示當前為執行同步代碼階段, 定義 resolve 和 reject 函數(忽略不分析), 此時我們可以發現 resolve 和 reject 都被 once 函數所封裝, 目的是讓被 once 包裝的任何函數的其中一個只執行一次, 保證 resolve 和 reject 兩者只能擇一并只執行一次。OK, 接著來到 factory 的執行, 其實就是執行官方示例中傳入的工廠函數, 這時候發起異步組件的請求。同步代碼繼續執行, sync置位false, 表示當前的同步代碼執行完畢, 然后返回undefined

這里可能會問怎么會返回undefined, 因為我們傳入的工廠函數沒有loading屬性, 然后當前的 factory 也沒有 resolved 屬性。

接著回到 createComponent 的代碼中:

if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    // 解析異步組件
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
    // Ctor為undefined則直接創建并返回異步組件的占位符組件Vnode
    if (Ctor === undefined) {
      return createAsyncPlaceholder(
        asyncFactory,
        data,
        context,
        children,
        tag
      )
    }
  }

因為剛才說 resolveAsyncComponent 執行返回了undefined, 所以執行 createAsyncPlaceholder 創建注釋vnode

這里可能還會問為什么要創建一個注釋vnode, 提前揭曉答案:

因為先要返回一個占位的 vnode, 等待異步請求加載后執行 forceUpdate 重新渲染, 然后這個節點會被更新渲染成組件的節點。

那繼續, 剛才答案說了, 當異步組件請求完成后, 則執行 resolve 并傳入對應的異步組件, 這時候 factory.resolved 被賦值為 ensureCtor 執行的返回結果, 就是一個組件構造器, 然后這時候 sync 為 false, 所以執行 forceRender, 而 forceRender 其實就是調用 vm.$forceUpdate 實現如下:

Vue.prototype.$forceUpdate = function () {
  const vm: Component = this
  if (vm._watcher) {
    vm._watcher.update()
  }
}

$forceUpdate 執行渲染 watcher 的 update 方法, 于是我們又會執行 createComponent 的方法, 執行 resolveAsyncComponent, 這時候 factory.resolved 已經定義過了, 于是直接返回 factory.resolved 的組件構造器。 于是就執行 createComponent 的后續組件的渲染和 patch 邏輯了。組件渲染和 patch 這里先不展開。

于是整個異步組件的流程就結束了。

工廠函數中返回Promise

先看下官網文檔提供的示例:

Vue.component(
  "async-webpack-example",
  // 這個 `import` 函數會返回一個 `Promise` 對象。
  () => import("./my-async-component")
)

由上面的示例, 可以看到當調用Vue.component的時候, definition為一個會返回 Promise 的函數, 與工廠函數執行 resolve 回調不同的地方在于:

export function resolveAsyncComponent (
  factory: Function,
  baseCtor: Class
): Class | void {

    ...省略

    // 執行工廠函數, 比如webpack獲取異步組件資源
    const res = factory(resolve, reject)
    if (isObject(res)) {
      // 為Promise對象,  import("./async-component")
      if (isPromise(res)) {
        // () => Promise
        if (isUndef(factory.resolved)) {
          res.then(resolve, reject)
        }
      } else if (isPromise(res.component)) {
        res.component.then(resolve, reject)

        if (isDef(res.error)) {
          factory.errorComp = ensureCtor(res.error, baseCtor)
        }

        if (isDef(res.loading)) {
          factory.loadingComp = ensureCtor(res.loading, baseCtor)
          if (res.delay === 0) {
            factory.loading = true
          } else {
            timerLoading = setTimeout(() => {
              timerLoading = null
              if (isUndef(factory.resolved) && isUndef(factory.error)) {
                factory.loading = true
                forceRender(false)
              }
            }, res.delay || 200)
          }
        }

        if (isDef(res.timeout)) {
          timerTimeout = setTimeout(() => {
            timerTimeout = null
            if (isUndef(factory.resolved)) {
              reject(
                process.env.NODE_ENV !== "production"
                  ? `timeout (${res.timeout}ms)`
                  : null
              )
            }
          }, res.timeout)
        }
      }
    }

    sync = false
    // return in case resolved synchronously
    return factory.loading
      ? factory.loadingComp
      : factory.resolved
  }
}

主要不同點在于執行完 factory 工廠函數, 這時候我們的工廠函數會返回一個 Promise, 所以 res.then(resolve, reject) 會執行, 接下來的過程也是等待異步組件請求完成, 然后執行 resolve 函數, 接著執行 forceRender 然后返回組件構造器。

這里 Promise 寫法的異步組件注冊過程和執行回調函數沒有太大的區別。

工廠函數返回一個配置化組件對象

同樣, 看下官網示例:

const AsyncComponent = () => ({
  // 需要加載的組件 (應該是一個 `Promise` 對象)
  component: import("./MyComponent.vue"),
  // 異步組件加載時使用的組件
  loading: LoadingComponent,
  // 加載失敗時使用的組件
  error: ErrorComponent,
  // 展示加載時組件的延時時間。默認值是 200 (毫秒)
  delay: 200,
  // 如果提供了超時時間且組件加載也超時了,
  // 則使用加載失敗時使用的組件。默認值是:`Infinity`
  timeout: 3000
})

從上面的示例可以看到, 工廠函數在執行成功后會返回一個配置對象, 這個對象的5個屬性我們都可以從官方文檔的注釋了解到各自的作用。那我們看一下這種方式和前面提到的兩種方式的區別在哪里.

export function resolveAsyncComponent (
  factory: Function,
  baseCtor: Class
): Class | void {
  // 高級異步組件使用
  if (isTrue(factory.error) && isDef(factory.errorComp)) {
    return factory.errorComp
  }

  if (isDef(factory.resolved)) {
    return factory.resolved
  }

  ...已了解過,省略

  if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
    return factory.loadingComp
  }

  if (owner && !isDef(factory.owners)) {
    const owners = factory.owners = [owner]
    let sync = true
    let timerLoading = null
    let timerTimeout = null

    ;(owner: any).$on("hook:destroyed", () => remove(owners, owner))

    const forceRender = (renderCompleted: boolean) => {...省略}
    // once讓被once包裝的任何函數的其中一個只執行一次
    const resolve = once((res: Object | Class) => {
      factory.resolved = ensureCtor(res, baseCtor)
      if (!sync) {
        forceRender(true)
      } else {
        owners.length = 0
      }
    })

    const reject = once(reason => {
      process.env.NODE_ENV !== "production" && warn(
        `Failed to resolve async component: ${String(factory)}` +
        (reason ? `
Reason: ${reason}` : "")
      )
      if (isDef(factory.errorComp)) {
        factory.error = true
        forceRender(true)
      }
    })

    // 執行工廠函數,比如webpack獲取異步組件資源
    const res = factory(resolve, reject)
    if (isObject(res)) {
      // 為Promise對象, import("./async-component")
      if (isPromise(res)) {
        ...省略
      } else if (isPromise(res.component)) {
        res.component.then(resolve, reject)

        if (isDef(res.error)) {
          factory.errorComp = ensureCtor(res.error, baseCtor)
        }

        if (isDef(res.loading)) {
          factory.loadingComp = ensureCtor(res.loading, baseCtor)
          if (res.delay === 0) {
            factory.loading = true
          } else {
            timerLoading = setTimeout(() => {
              timerLoading = null
              if (isUndef(factory.resolved) && isUndef(factory.error)) {
                factory.loading = true
                forceRender(false)
              }
            }, res.delay || 200)
          }
        }

        if (isDef(res.timeout)) {
          timerTimeout = setTimeout(() => {
            timerTimeout = null
            if (isUndef(factory.resolved)) {
              reject(
                process.env.NODE_ENV !== "production"
                  ? `timeout (${res.timeout}ms)`
                  : null
              )
            }
          }, res.timeout)
        }
      }
    }

    sync = false
    // return in case resolved synchronously
    return factory.loading
      ? factory.loadingComp
      : factory.resolved
  }
}

渲染過程同樣來到 resolveAsyncComponent, 一開始判斷 factory.error 是否為 true, 當然一開始肯定是 false 的, 不進入該邏輯, 接著同樣執行到 const res = factory(resolve, reject) 的執行, 因為我們剛才說了我們的工廠函數返回了一個異步組件配置對象, 于是 res 就是我們定義該工廠函數返回的對象, 這時候 isObject(res) 為 true, isPromise(res) 為 false, isPromise(res.component) 為 true, 接著判斷 res.error 是否有定義, 于是在 factory 定義擴展了 errorComp, errorComp是通過 ensureCtor 來對 res.error 的定義組件轉化為組件的構造器, loading 也是一樣的邏輯, 在 factory 擴展 loadingComp 組件構造器。

接著, 這時候需要特別注意, 當我們定義的 res.delay 為 0, 則直接把 factory.loading 置為 true, 因為這里影響到 resolveAsyncComponent 的返回值。

return factory.loading
      ? factory.loadingComp
      : factory.resolved

當 factory.loading 為 true, 會返回 loadingComp, 使得 createComponet 的時候不是創建一個注釋vnode, 而是直接執行 loadingComp 的渲染。

如果我們的 res.delay 不為0, 則會啟用一個計時器, 先同步返回 undefined 觸發注釋節點創建, 在一定的時間后執行 factory.loading = true 和 forceRender(false), 條件是組件沒有加載完成以及沒有出錯 reject, 接著執行把注釋vnode 替換為加載過程組件 loadingComp 的渲染。

而 res.timeout 主要用來計時, 當在 res.timeout 的時間內, 如果當前的 factory.resolved 為 undefined, 則說明異步組件加載已經超時了, 于是會調用 reject 方法, reject 其實就是調用 forceRender 來執行 errorComp 的渲染。

OK, 當我們的組件加載完成了, 執行了 resolve 方法, factory.resloved 置為 true, 調用 forceRender 來把注釋節點或者是 loadingComp 的節點替換渲染為加載完成的組件。

到此, 我們已經了解三種異步組件的注冊過程了。

小結一下

異步組件的渲染本質上其實就是執行2次或者2次以上的渲染, 先把當前組件渲染為注釋節點, 當組件加載成功后, 通過 forceRender 執行重新渲染。或者是渲染為注釋節點, 然后再渲染為loading節點, 在渲染為請求完成的組件。

這里需要注意的是 forceRender 的執行, forceRender 用于強制執行當前的節點重新渲染, 至于整個渲染過程是怎么樣的后續文章有機會的話。。。再講解吧。

本人語文表達能力有限, 只是突發奇想為了把自己了解到的過程用自己的話語表達出來, 如果有什么錯誤的地方望多多包涵。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/104698.html

相關文章

  • Vue原理】NextTick - 源碼 服務Vue

    寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧 【Vue原理】NextTick - 源碼版 之 服務Vue 初次看的兄弟可以先看 【Vue原理】NextTick - 白話版 簡單了解下...

    Acceml 評論0 收藏0
  • 前方來報,八月最新資訊--關于vue2&3的最佳文章推薦

    摘要:哪吒別人的看法都是狗屁,你是誰只有你自己說了才算,這是爹教我的道理。哪吒去他個鳥命我命由我,不由天是魔是仙,我自己決定哪吒白白搭上一條人命,你傻不傻敖丙不傻誰和你做朋友太乙真人人是否能夠改變命運,我不曉得。我只曉得,不認命是哪吒的命。 showImg(https://segmentfault.com/img/bVbwiGL?w=900&h=378); 出處 查看github最新的Vue...

    izhuhaodev 評論0 收藏0
  • 淺析 Vue 2.6 中的 nextTick 方法

    摘要:核心的異步延遲函數,用于異步延遲調用函數優先使用原生原本支持更廣,但在的中,觸摸事件處理程序中觸發會產生嚴重錯誤的,回調被推入隊列但是隊列可能不會如期執行。 淺析 Vue 2.6 中的 nextTick 方法。 事件循環 JS 的 事件循環 和 任務隊列 其實是理解 nextTick 概念的關鍵。這個網上其實有很多優質的文章做了詳細介紹,我就簡單過過了。 以下內容適用于瀏覽器端 JS,...

    fobnn 評論0 收藏0
  • 淺析webpack源碼Compiler.js模塊(八)

    摘要:小尾巴最終返回了屬性掛載把引入的函數模塊全部暴露出來下面暴露了一些插件再通俗一點的解釋比如當你你能調用文件下的方法這個和上面的不同在于上面的是掛在函數對象上的正題要想理解必須要理解再寫一遍地址我們先簡單的理解它為一個通過注冊插件是插件的事 webpack.js小尾巴 const webpack = (options, callback) => { //... if (...

    PumpkinDylan 評論0 收藏0
  • Vue原理】NextTick - 源碼 獨立自身

    摘要:盡量把所有異步代碼放在一個宏微任務中,減少消耗加快異步代碼的執行。我們知道,如果一個異步代碼就注冊一個宏微任務的話,那么執行完全部異步代碼肯定慢很多避免頻繁地更新。中就算我們一次性修改多次數據,頁面還是只會更新一次。 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5...

    劉東 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<