溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

vue高階組件有哪些

發(fā)布時間:2022-12-20 17:39:03 來源:億速云 閱讀:168 作者:栢白 欄目:web開發(fā)

今天小編給大家分享的是vue高階組件有哪些,相信很多人都不太了解,為了讓大家更加了解,所以給大家總結(jié)了以下內(nèi)容,一起往下看吧。一定會有所收獲的哦。

在vue中,高階組件其實就是一個高階函數(shù), 即返回一個組件函數(shù)的函數(shù)。高階組件的特點:1、是無副作用的純函數(shù),且不應該修改原組件,即原組件不能有變動;2、不關心傳遞的數(shù)據(jù)(props)是什么,并且新生成組件不關心數(shù)據(jù)來源;3、接收到的props應該傳遞給被包裝組件,即直接將原組件prop傳給包裝組件;4、高階組件完全可以添加、刪除、修改props。

vue高階組件有哪些

高階組件介紹

vue 高階組件的認識,在React中組件是以復用代碼實現(xiàn)的,而Vue中是以mixins 實現(xiàn),并且官方文檔中也缺少一些高階組件的概念,因為在vue中實現(xiàn)高階組很困難,并不像React簡單,其實vue中mixins也同樣和以代替,在讀了一部分源碼之后,對vue有了更深的認識

所謂高階組件其實就是一個高階函數(shù), 即返回一個組件函數(shù)的函數(shù),Vue中怎么實現(xiàn)呢? 注意 高階組件有如下特點

高階組件(HOC)應該是無副作用的純函數(shù),且不應該修改原組件,即原組件不能有變動
高階組件(HOC)不關心你傳遞的數(shù)據(jù)(props)是什么,并且新生成組件不關心數(shù)據(jù)來源
高階組件(HOC)接收到的 props 應該傳遞給被包裝組件即直接將原組件prop傳給包裝組件
高階組件完全可以添加、刪除、修改 props

高階組件舉例

Base.vue

<template>
  <div>
    <p @click="Click">props: {{test}}</p>
  </div>
</template>
<script>
export default {
  name: 'Base',
  props: {
    test: Number
  },
  methods: {
    Click () {
      this.$emit('Base-click')
    }
  }
}
</script>

Vue 組件主要就是三點:props、event 以及 slots。對于 Base組件 組件而言,它接收一個數(shù)字類型的 props 即 test,并觸發(fā)一個自定義事件,事件的名稱是:Base-click,沒有 slots。我們會這樣使用該組件:

現(xiàn)在我們需要 base-component 組件每次掛載完成的時候都打印一句話:haha,同時這也許是很多組件的需求,所以按照 mixins 的方式,我們可以這樣做,首先定義個 mixins

export default consoleMixin {
  mounted () {
    console.log('haha')
  }
}

然后在 Base 組件中將 consoleMixin 混入:

<template>
  <div>
    <p @click="Click">props: {{test}}</p>
  </div>
</template>
<script>
export default {
  name: 'Base',
  props: {
    test: Number
  },
  mixins: [ consoleMixin ],
  methods: {
    Click () {
      this.$emit('Base-click')
    }
  }
}
</script>

這樣使用 Base 組件的時候,每次掛載完成之后都會打印一句 haha,不過現(xiàn)在我們要使用高階組件的方式實現(xiàn)同樣的功能,回憶高階組件的定義:接收一個組件作為參數(shù),返回一個新的組件,那么此時我們需要思考的是,在 Vue 中組件是什么?Vue 中組件是函數(shù),不過那是最終結(jié)果,比如我們在單文件組件中的組件定義其實就是一個普通的選項對象,如下:

export default {
  name: 'Base',
  props: {...},
  mixins: [...]
  methods: {...}
}

這難道不是一個純對象嘛

import Base from './Base.vue'
console.log(Base)

這里的Base是什么呢 對就是一個JSON對象,而當以把他加入到一個組件的components,Vu最終會以該參數(shù)即option來構(gòu)造實例的構(gòu)造函數(shù),所以Vue中組件就是個函數(shù),但是在引入之前仍只是一個options對象,所以這樣就很好明白了 Vue中組件開始只是一個對象,即高階組件就是 一個函數(shù)接受一個純對象,并且返回一個新純對象

export default function Console (BaseComponent) {
  return {
    template: '<wrapped v-on="$listeners" v-bind="$attrs"/>',
    components: {
      wrapped: BaseComponent
    },
    mounted () {
      console.log('haha')
    }
  }
}

這里 Console就是一個高階組件,它接受一個參數(shù) BaseComponent即傳入的組件,返回一個新組件,將BaseComponent作為新組件的子組件并且在mounted里設置鉤子函數(shù) 打印haha,我們可以完成mixins同樣做到的事,我們并沒有修改子組件Base,這里的 $listeners $attrs 其實是在透傳props 和事件 那這樣真的就完美解決問題了嗎?不是的,首先 template 選項只有在完整版的 Vue 中可以使用,在運行時版本中是不能使用的,所以最起碼我們應該使用渲染函數(shù)(render)替代模板(template)

Console.js

export default function Console (BaseComponent) {
  return {
    mounted () {
      console.log('haha')
    },
    render (h) {
      return h(BaseComponent, {
        on: this.$listeners,
        attrs: this.$attrs,
      })
    }
  }
}

我們將模板改寫成了渲染函數(shù),看上去沒什么問題,實際還是有問題,上面的代碼中 BaseComponent 組件依然收不到 props,為什么呢,我們不是已經(jīng)在 h 函數(shù)的第二個參數(shù)中將 attrs 傳遞過去了嗎,怎么還收不到?當然收不到,attrs 指的是那些沒有被聲明為 props 的屬性,所以在渲染函數(shù)中還需要添加 props 參數(shù):

export default function Console (BaseComponent) {
  return {
    mounted () {
      console.log('haha')
    },
    render (h) {
      return h(BaseComponent, {
        on: this.$listeners,
        attrs: this.$attrs,
        props: this.$props
      })
    }
  }
}

那這樣呢 其實還是不行 props始終是空對象,這里的props是高階組件的對象,但是高階組件并沒有聲明props所以如此故要再聲明一個props

export default function Console (BaseComponent) {
  return {
    mounted () {
      console.log('haha')
    },
    props: BaseComponent.props,
    render (h) {
      return h(BaseComponent, {
        on: this.$listeners,
        attrs: this.$attrs,
        props: this.$props
      })
    }
  }
}

ok 一個差不多的高階組件就完成了 但是能還每完 我們只實現(xiàn)了 透傳props,透傳事件,emmmm就剩下slot了 我們修改 Base 組件為其添加一個具名插槽和默認插槽 Base.vue

<template>
  <div>
    <span @click="handleClick">props: {{test}}</span>
    <slot name="slot1"/> <!-- 具名插槽 --></slot>
    <p>===========</p>
    <slot><slot/> <!-- 默認插槽 -->
  </div>
</template>
 
<script>
export default {
  ...
}
</script>

<template>
  <div>
    <Base>
      <h3 slot="slot1">BaseComponent slot</h3>
      <p>default slot</p>
    </Base>
    <wrapBase>
      <h3 slot="slot1">EnhancedComponent slot</h3>
      <p>default slot</p>
    </wrapBase>
  </div>
</template>
 
<script>
  import Base from './Base.vue'
  import hoc from './Console.js'
 
  const wrapBase = Console(Base)
 
  export default {
    components: {
      Base,
      wrapBase
    }
  }
</script>

這里的執(zhí)行結(jié)果就是 wrapBase里的slot都沒有了 所以就要改一下高階組建了

function Console (BaseComponent) {
  return {
    mounted () {
      console.log('haha')
    },
    props: BaseComponent.props,
    render (h) {
 
      // 將 this.$slots 格式化為數(shù)組,因為 h 函數(shù)第三個參數(shù)是子節(jié)點,是一個數(shù)組
      const slots = Object.keys(this.$slots)
        .reduce((arr, key) => arr.concat(this.$slots[key]), [])
 
      return h(BaseComponent, {
        on: this.$listeners,
        attrs: this.$attrs,
        props: this.$props
      }, slots) // 將 slots 作為 h 函數(shù)的第三個參數(shù)
    }
  }
}

這時 slot內(nèi)容確實渲染出來了 但是順序不太對 高階組件的全部渲染到了末尾。。 其實 Vue在處理具名插槽會考慮作用域的因素 首先 Vue 會把模板(template)編譯成渲染函數(shù)(render),比如如下模板:

<div>
  <p slot="slot1">Base slot</p>
</div>

會被編譯成如下渲染函數(shù):

var render = function() {
  var _vm = this
  var _h = _vm.$createElement
  var _c = _vm._self._c || _h
  return _c("div", [
    _c("div", {
      attrs: { slot: "slot1" },
      slot: "slot1"
    }, [
      _vm._v("Base slot")
    ])
  ])
}

觀察上面的渲染函數(shù)我們發(fā)現(xiàn)普通的 DOM 是通過 _c 函數(shù)創(chuàng)建對應的 VNode 的。現(xiàn)在我們修改模板,模板中除了有普通 DOM 之外,還有組件,如下:

<div>
  <Base>
    <p slot="slot1">Base slot</p>
    <p>default slot</p>
  </Base>
</div>

其render函數(shù)

var render = function() {
  var _vm = this
  var _h = _vm.$createElement
  var _c = _vm._self._c || _h
  return _c(
    "div",
    [
      _c("Base", [
        _c("p", { attrs: { slot: "slot1" }, slot: "slot1" }, [
          _vm._v("Base slot")
        ]),
        _vm._v(" "),
        _c("p", [_vm._v("default slot")])
      ])
    ],
  )
}

我們發(fā)現(xiàn)無論是普通DOM還是組件,都是通過 _c 函數(shù)創(chuàng)建其對應的 VNode 的 其實 _c 在 Vue 內(nèi)部就是 createElement 函數(shù)。createElement 函數(shù)會自動檢測第一個參數(shù)是不是普通DOM標簽如果不是普通DOM標簽那么 createElement 會將其視為組件,并且創(chuàng)建組件實例,注意組件實例是這個時候才創(chuàng)建的 但是創(chuàng)建組件實例的過程中就面臨一個問題:組件需要知道父級模板中是否傳遞了 slot 以及傳遞了多少,傳遞的是具名的還是不具名的等等。那么子組件如何才能得知這些信息呢?很簡單,假如組件的模板如下

<div>
  <Base>
    <p slot="slot1">Base slot</p>
    <p>default slot</p>
  </Base>
</div>

父組件的模板最終會生成父組件對應的 VNode,所以以上模板對應的 VNode 全部由父組件所有,那么在創(chuàng)建子組件實例的時候能否通過獲取父組件的 VNode 進而拿到 slot 的內(nèi)容呢?即通過父組件將下面這段模板對應的 VNode 拿到

<Base>
    <p slot="slot1">Base slot</p>
    <p>default slot</p>
  </Base>

如果能夠通過父級拿到這段模板對應的 VNode,那么子組件就知道要渲染哪些 slot 了,其實 Vue 內(nèi)部就是這么干的,實際上你可以通過訪問子組件的 this.$vnode 來獲取這段模板對應的 VNode

this.$vnode 并沒有寫進 Vue 的官方文檔

子組件拿到了需要渲染的 slot 之后進入到了關鍵的一步,這一步就是導致高階組件中透傳 slot 給 Base組件 卻無法正確渲染的原因 children的VNode中的context引用父組件實例 其本身的context也會引用本身實例 其實是一個東西

console.log(this. vnode.context===this.vnode.componentOptions.children[0].context) //ture

而 Vue 內(nèi)部做了一件很重要的事兒,即上面那個表達式必須成立,才能夠正確處理具名 slot,否則即使 slot 具名也不會被考慮,而是被作為默認插槽。這就是高階組件中不能正確渲染 slot 的原因

即 高階組件中 本來時父組件和子組件之間插入了一個組件(高階組件),而子組件的 this.$vnode其實是高階組件的實例,但是我們將slot透傳給子組件,slot里 VNode 的context實際引用的還是父組件 所以

console.log(this.vnode.context === this.vnode.componentOptions.children[0].context) // false

最終導致具名插槽被作為默認插槽,從而渲染不正確。

決辦法也很簡單,只需要手動設置一下 slot 中 VNode 的 context 值為高階組件實例即可

function Console (Base) {
  return {
    mounted () {
      console.log('haha')
    },
    props: Base.props,
    render (h) {
      const slots = Object.keys(this.$slots)
        .reduce((arr, key) => arr.concat(this.$slots[key]), [])
        // 手動更正 context
        .map(vnode => {
          vnode.context = this._self //綁定到高階組件上
          return vnode
        })
 
      return h(WrappedComponent, {
        on: this.$listeners,
        props: this.$props,
        attrs: this.$attrs
      }, slots)
    }
  }
}

說明白就是強制把slot的歸屬權(quán)給高階組件 而不是 父組件 通過當前實例 _self 屬性訪問當實例本身,而不是直接使用 this,因為 this 是一個代理對象

關于vue高階組件有哪些就分享到這里了,希望以上內(nèi)容可以對大家有一定的參考價值,可以學以致用。如果喜歡本篇文章,不妨把它分享出去讓更多的人看到。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI