欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue組件的渲染流程詳細(xì)講解

 更新時間:2022年06月28日 10:45:16   作者:不在五行中  
在Vue核心中除了響應(yīng)式原理外,視圖渲染也是重中之重,下面這篇文章主要給大家介紹了關(guān)于Vue組件的渲染流程,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下

注: 本文目的是通過源碼方向來講component組件的渲染流程

引言與例子

在我們創(chuàng)建Vue實(shí)例時,是通過new Vue、this._init(options)方法來進(jìn)行初始化,然后再執(zhí)行$mount等,那么在組件渲染時,可不可以讓組件使用同一套邏輯去處理呢?

答:當(dāng)然是可以的,需要使用到Vue.extend方法來實(shí)現(xiàn)。

舉一個工作中能用到的例子:

需求:我們在項(xiàng)目中實(shí)現(xiàn)一個像element-uiMessage Box彈窗,在全局注冊(Vue.use)后,能像alert方法一樣,調(diào)用函數(shù)就可以彈出

實(shí)現(xiàn)

(先簡單說下vueuse方法基礎(chǔ)使用,use注冊時,如果是函數(shù)會執(zhí)行函數(shù),如果是對象,會執(zhí)行對象中的install方法進(jìn)行注冊)

根據(jù)需求,我們在調(diào)用use方法后,需要實(shí)現(xiàn)兩個目的:將組件注冊并直接掛載到dom上,將方法放在Vue.prototype下;

  • 首先實(shí)現(xiàn)彈窗樣式和邏輯(不是本文主要目的,此處跳過),假設(shè)其中有一個簡單的顯示函數(shù)show(){this.visible = true}
  • 要通過use的方式注冊組件,就要有一個install方法,在方法中首先調(diào)用Vue.extend(messageBox組件),然后調(diào)用該對象的$mount()方法進(jìn)行渲染,最后將生成的DOM節(jié)點(diǎn)messageBox.$el上樹,然后上show方法放到Vue.prototype上,就完成了
function install(Vue) {
    // 生成messageBox 構(gòu)造函數(shù)
    var messageBox = Vue.extend(this);
    messageBox = new messageBox();
    // 掛載組件,生成dom節(jié)點(diǎn)(這里沒傳參,所以只是生成dom并沒有上樹)
    messageBox.$mount();
    // 節(jié)點(diǎn)上樹
    document.body.appendChild(messageBox.$el);
    // 上show方法掛載到全局
    Vue.prototype.$showMessageBox = messageBox.show;
}

根據(jù)例子,我們來看一下這個extend方法:

extend

Vue中,有一個extend方法,組件的渲染就是通過調(diào)用extend創(chuàng)建一個繼承于Vue的構(gòu)造函數(shù)。
extend中的創(chuàng)建的主要過程是:

在內(nèi)部創(chuàng)建一個最終要返回的構(gòu)造函數(shù)Sub,Sub函數(shù)內(nèi)部與Vue函數(shù)相同,都是調(diào)用this._init(options) 繼承Vue,合并Vue.options和組件的optionsSub上賦值靜態(tài)方法 緩存Sub構(gòu)造函數(shù),并在extend方法開始時判斷緩存,避免重復(fù)渲染同一組件 返回Sub構(gòu)造函數(shù)(要注意extend調(diào)用后返回的是個還未執(zhí)行的構(gòu)造函數(shù) Sub)

// 注:mergeOptions方法是通過不同的策略,將options中的屬性進(jìn)行合并

Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this // 父級構(gòu)造函數(shù)
    // 拿到cid,并通過_Ctor屬性緩存,判斷是否已經(jīng)創(chuàng)建過,避免重復(fù)渲染同一組件
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    // name校驗(yàn)+拋出錯誤
    const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name)
    }

    // 創(chuàng)建構(gòu)造函數(shù)Sub
    const Sub = function VueComponent (options) {
      this._init(options)
    }
    // 繼承原型對象
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++ // cid自增
    // 父級options與當(dāng)前傳入的組件options合并
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super // 緩存父級構(gòu)造函數(shù)

    // For props and computed properties, we define the proxy getters on the Vue instances at extension time, on the extended prototype. This avoids Object.defineProperty calls for each instance created.
    // 對于props和computed屬性,我們在擴(kuò)展時在擴(kuò)展原型的Vue實(shí)例上定義代理getter。這避免了object。為創(chuàng)建的每個實(shí)例調(diào)用defineProperty。
    if (Sub.options.props) {
      initProps(Sub)
    }
    if (Sub.options.computed) {
      initComputed(Sub)
    }

    // 將全局方法放在Sub上,允許進(jìn)一步調(diào)用
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)

    // 對應(yīng)上邊的_Ctor屬性緩存
    cachedCtors[SuperId] = Sub
    return Sub
  }
}

看完export函數(shù)后,思考下,生成組件時是一個怎樣的執(zhí)行流程呢?

執(zhí)行流程

1. 注冊流程(以Vue.component()祖冊為例子):

用戶在調(diào)用Vue.component時,其實(shí)就只執(zhí)行了三行代碼

// 簡化版component源碼
Vue.component = function (id,definition) {
    definition.name = definition.name || id
    // _base指向的是new Vue()時的這個Vue實(shí)例,調(diào)用的是Vue實(shí)例上的extend方法
    definition = this.options._base.extend(definition)
    this.options.components[id] = definition
    return definition
}

獲取并賦值組件的name definition.name調(diào)用根Vue上的extend方法
將組件放到options.components
返回definition

(如果是異步組件的話,只會走后邊兩步,不會執(zhí)行extend)

在下文中,我們會將extend方法返回的Sub對象稱為Ctor

在創(chuàng)建組件時,我們實(shí)際只是為組件執(zhí)行了extend方法,但在option.components中傳入的組件不會被執(zhí)行extend方法,在3.渲染流程中會執(zhí)行

2. 執(zhí)行流程

createElement函數(shù)執(zhí)行時,根據(jù)tag字段來判斷是不是一個組件,如果是組件,執(zhí)行組件初始化方法createComponent

createComponent

  • 首先判斷傳入的Ctor是否已經(jīng)執(zhí)行了extend方法,沒有執(zhí)行的話執(zhí)行一遍
  • 然后判斷是不是異步組件(如果是,調(diào)用createAsyncPlaceholder生成并返回)
  • 然后處理data,創(chuàng)建data.hook中的鉤子函數(shù),比如init
  • 最后調(diào)用new VNode()生成節(jié)點(diǎn)

先看下createElement函數(shù)源碼,然后在底下主要說下init函數(shù)

export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  if (isUndef(Ctor)) {
    return
  }

  // _base指向的是new Vue()時的這個Vue實(shí)例
  const baseCtor = context.$options._base

  // 如果extend沒有執(zhí)行過,在這里執(zhí)行
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }

  // 報錯處理
  if (typeof Ctor !== 'function') {
    if (process.env.NODE_ENV !== 'production') {
      warn(`Invalid Component definition: ${String(Ctor)}`, context)
    }
    return
  }

  // 異步處理
  let asyncFactory
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
    if (Ctor === undefined) {
      // return a placeholder node for async component, which is rendered
      // as a comment node but preserves all the raw information for the node.
      // the information will be used for async server-rendering and hydration.
      return createAsyncPlaceholder(
        asyncFactory,
        data,
        context,
        children,
        tag
      )
    }
  }

  // 處理data
  data = data || {}

  
  resolveConstructorOptions(Ctor)
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }
  const listeners = data.on
  data.on = data.nativeOn
  if (isTrue(Ctor.options.abstract)) {
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }
  
  // 重點(diǎn) 創(chuàng)建init方法
  installComponentHooks(data)

  // return a placeholder vnode
  const name = Ctor.options.name || tag
  // 得到vnode
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )
  return vnode
}

讓我們看下init方法

init,prepatch,insert,destroy等方法在源碼中是創(chuàng)建在componentVNodeHooks對象上,通過installComponentHooksinstallComponentHooks方法判斷data.hook中是否有該值,然后進(jìn)行合并處理等操作實(shí)現(xiàn)的,在這里,我們不考慮其他的直接看init方法

先放上完整代碼:

init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      // 掛載到vnode上,方便取值
      // 在這個函數(shù)中會new并返回extend生成的Ctor
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      // 重點(diǎn)
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  }
  
// createComponentInstanceForVnode函數(shù)示例
export function createComponentInstanceForVnode (
  vnode: any, // we know it's MountedComponentVNode but flow doesn't
  parent: any, // activeInstance in lifecycle state
): Component {
  const options: InternalComponentOptions = {
    _isComponent: true,
    _parentVnode: vnode,
    parent
  }
  // check inline-template render functions
  const inlineTemplate = vnode.data.inlineTemplate
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render
    options.staticRenderFns = inlineTemplate.staticRenderFns
  }
  return new vnode.componentOptions.Ctor(options)
}
  • init方法中,執(zhí)行createComponentInstanceForVnode時會調(diào)用new Ctor(options)
  • 在上邊介紹extend方法中可以看到new Ctor時會調(diào)用Vue_init方法,執(zhí)行Vue實(shí)例的初始化邏輯
  • Vue.prototype._init方法初始化完畢,執(zhí)行$mount是,會有下邊代碼這樣一個判斷,組件這時沒有el,所以不會執(zhí)行$mount函數(shù)
if (vm.$options.el) {
    vm.$mount(vm.$options.el);
}
  • 手動執(zhí)行$mount函數(shù)

3. 渲染流程

在組件渲染流程createElm函數(shù)中,有一段代碼

if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
  return
}

所以,組件的生成和判斷都是在createComponent函數(shù)中發(fā)生的

createComponent

  • 因?yàn)樵趫?zhí)行流程中,生成的vnode就是該函數(shù)中傳入的vnode,并且在vnode創(chuàng)建時把data放在了vnode上,那么vnode.data.hook.init就可以獲取到上邊說的init函數(shù),我們可以判斷,如果有該值,就可以認(rèn)定本次vnode為組件,并執(zhí)行vnode.data.hook.init,init的內(nèi)容詳見上邊
  • init執(zhí)行完畢后,Ctor的實(shí)例會被掛載到vnode.componentInstance上,并且已經(jīng)生成了真實(shí)dom,可以在vnode.componentInstance.$el上獲取到
  • 最后執(zhí)行initComponentinsert,將組件掛載
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    let i = vnode.data
    if (isDef(i)) {
      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive

      // 在判斷是否定義的同時,把變量做了改變,最終拿到了i.hook.init(在extend函數(shù)中注冊的Ctor的init方法)
      if (isDef(i = i.hook) && isDef(i = i.init)) {
        // 執(zhí)行init
        i(vnode, false /* hydrating */)
      }
      // after calling the init hook, if the vnode is a child component
      // it should've created a child instance and mounted it. the child
      // component also has set the placeholder vnode's elm.
      // in that case we can just return the element and be done.
      //調(diào)用init hook之后,如果vnode是子組件
      //它應(yīng)該創(chuàng)建一個子實(shí)例并掛載它。孩子
      //組件還設(shè)置了占位符vnode的elm。
      //在這種情況下,我們只需返回元素就可以了。

      // componentInstance是組件的ctor實(shí)例,有了代表已經(jīng)創(chuàng)建了vnode.elm(真實(shí)節(jié)點(diǎn))
      if (isDef(vnode.componentInstance)) {
        initComponent(vnode, insertedVnodeQueue)
        insert(parentElm, vnode.elm, refElm)
        if (isTrue(isReactivated)) {
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
        }
        return true
      }
    }
  }

總結(jié)

到此這篇關(guān)于Vue組件渲染流程的文章就介紹到這了,更多相關(guān)Vue組件渲染流程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • lottie實(shí)現(xiàn)vue自定義loading指令及常用指令封裝詳解

    lottie實(shí)現(xiàn)vue自定義loading指令及常用指令封裝詳解

    這篇文章主要為大家介紹了lottie實(shí)現(xiàn)vue自定義loading指令及常用指令封裝,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • vue3項(xiàng)目目錄結(jié)構(gòu)示例詳解

    vue3項(xiàng)目目錄結(jié)構(gòu)示例詳解

    更好的了解項(xiàng)目的目錄結(jié)構(gòu),能更好的去開發(fā)項(xiàng)目,下面這篇文章主要給大家介紹了關(guān)于vue3項(xiàng)目目錄結(jié)構(gòu)的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-02-02
  • vue實(shí)現(xiàn)動態(tài)控制表格列的顯示隱藏

    vue實(shí)現(xiàn)動態(tài)控制表格列的顯示隱藏

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)動態(tài)控制表格列的顯示隱藏,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • vue-element的select下拉框賦值實(shí)例

    vue-element的select下拉框賦值實(shí)例

    這篇文章主要介紹了vue-element的select下拉框賦值實(shí)例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • vue緩存的keepalive頁面刷新數(shù)據(jù)的方法

    vue緩存的keepalive頁面刷新數(shù)據(jù)的方法

    這篇文章主要介紹了vue緩存的keepalive頁面刷新數(shù)據(jù)的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-04-04
  • vue-resource調(diào)用promise取數(shù)據(jù)方式詳解

    vue-resource調(diào)用promise取數(shù)據(jù)方式詳解

    這篇文章主要介紹了vue-resource調(diào)用promise取數(shù)據(jù)方式詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-07-07
  • vue中添加語音播報功能的實(shí)現(xiàn)

    vue中添加語音播報功能的實(shí)現(xiàn)

    本文主要介紹了vue中添加語音播報功能的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • 詳解Vue3的虛擬DOM是如何生成的

    詳解Vue3的虛擬DOM是如何生成的

    這篇文章給大家詳細(xì)介紹了 Vue3 的虛擬DOM生成規(guī)則,文章通過代碼示例和圖片介紹的非常詳細(xì),具有一定的參考價值,對我們的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2023-09-09
  • 詳解Vue+axios+Node+express實(shí)現(xiàn)文件上傳(用戶頭像上傳)

    詳解Vue+axios+Node+express實(shí)現(xiàn)文件上傳(用戶頭像上傳)

    這篇文章主要介紹了詳解Vue+axios+Node+express實(shí)現(xiàn)文件上傳(用戶頭像上傳),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08
  • vue項(xiàng)目中openlayers繪制行政區(qū)劃

    vue項(xiàng)目中openlayers繪制行政區(qū)劃

    這篇文章主要為大家詳細(xì)介紹了vue項(xiàng)目中openlayers繪制行政區(qū)劃,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-12-12

最新評論