最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

在Vue中使用HOC模式的實(shí)現(xiàn)

 更新時(shí)間:2020年08月23日 17:21:00   作者:zhangwinwin  
這篇文章主要介紹了在Vue中使用HOC模式的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言

HOC是React常用的一種模式,但HOC只能是在React才能玩嗎?先來(lái)看看React官方文檔是怎么介紹HOC的:

高階組件(HOC)是React中用于復(fù)用組件邏輯的一種高級(jí)技巧。HOC自身不是ReactAPI的一部分,它是一種基于React的組合特性而形成的設(shè)計(jì)模式。

HOC它是一個(gè)模式,是一種思想,并不是只能在React中才能用。所以結(jié)合Vue的特性,一樣能在Vue中玩HOC。

HOC

HOC要解決的問(wèn)題

并不是說(shuō)哪種技術(shù)新穎,就得使用哪一種。得看這種技術(shù)能夠解決哪些痛點(diǎn)。

HOC主要解決的是可復(fù)用性的問(wèn)題。在Vue中,這種問(wèn)題一般是用Mixin解決的。Mixin是一種通過(guò)擴(kuò)展收集功能的方式,它本質(zhì)上是將一個(gè)對(duì)象的屬性拷貝到另一個(gè)對(duì)象上去。

最初React也是使用Mixin的,但是后面發(fā)現(xiàn)Mixin在React中并不是一種好的模式,它有以下的缺點(diǎn):

  • mixin與組件之間容易導(dǎo)致命名沖突
  • mixin是侵入式的,改變了原組件,復(fù)雜性大大提高。

所以React就慢慢的脫離了mixin,從而推薦使用HOC。并不是mixin不優(yōu)秀,只是mixin不適合React。

HOC是什么

HOC全稱:high-order component--也就是高階組件。具體而言,高階組件是參數(shù)為組件,返回值為新組件的函數(shù)。

而在React和Vue中組件就是函數(shù),所以的高階組件其實(shí)就是高階函數(shù),也就是返回一個(gè)函數(shù)的函數(shù)。

來(lái)看看HOC在React的用法:

function withComponent(WrappedComponent) {
  return class extends Component {
    componentDidMount () {
      console.log('已經(jīng)掛載完成')
    }
    render() {
      return <WrappedComponent {...props} />;
    }
  }
}

withComponent就是一個(gè)高階組件,它有以下特點(diǎn):

  • HOC是一個(gè)純函數(shù),且不應(yīng)該修改原組件
  • HOC不關(guān)心傳遞的props是什么,并且WrappedComponent不關(guān)心數(shù)據(jù)來(lái)源
  • HOC接收到的props應(yīng)該透?jìng)鹘oWrapperComponent

在Vue中使用HOC

怎么樣才能將Vue上使用HOC的模式呢?

我們一般書(shū)寫(xiě)的Vue組件是這樣的:

<template>
 <div>
  <p>{{title}}</p>
  <button @click="changeTitle"></button>
 </div>
</template>

<script>
export default {
 name: 'ChildComponent',
 props: ['title'],
 methods: {
  changeTitle () {
    this.$emit('changeTitle');
  }
 }
}
</script>

而withComponet函數(shù)的功能是在每次掛載完成后都打印一句:已經(jīng)掛載完成。

既然HOC是替代mixin的,所以我們先用mixin書(shū)寫(xiě)一遍:

export default {
  mounted () {
    console.log('已經(jīng)掛載完成')
  }
}

然后導(dǎo)入到ChildComponent中

import withComponent from './withComponent';
export default {
  ...
  mixins: ['withComponet'],
}

對(duì)于這個(gè)組件,我們?cè)诟附M件中是這樣調(diào)用的

<child-component :title='title' @changeTitle='changeTitle'></child-component>

<script>
import ChildComponent from './childComponent.vue';
export default {
  ...
  components: {ChildComponent}
}
</script>

大家有沒(méi)有發(fā)現(xiàn),當(dāng)我們導(dǎo)入一個(gè)Vue組件時(shí),其實(shí)是導(dǎo)入一個(gè)對(duì)象。

export default {}

至于說(shuō)組件是函數(shù),其實(shí)是經(jīng)過(guò)處理之后的結(jié)果。所以Vue中的高階組件也可以是:接收一個(gè)純對(duì)象,返回一個(gè)純對(duì)象。

所以改為HOC模式,是這樣的:

export default function withComponent (WrapperComponent) {
  return {
    mounted () {
      console.log('已經(jīng)掛載完成')
    },
    props: WrappedComponent.props,
    render (h) {
      return h(WrapperComponent, {
        on: this.$listeners,
        attrs: this.$attrs,
        props: this.$props
      })
    }
  }
}

注意{on: this.$listeners,attr: this.$attrs, props: this.props}這一句就是透?jìng)鱬rops的原理,等價(jià)于React中的<WrappedComponent {...props} />;

this.$props是指已經(jīng)被聲明的props屬性,this.$attrs是指沒(méi)被聲明的props屬性。這一定要兩個(gè)一起透?jìng)?,缺少哪一個(gè),props都不完整。

為了通用性,這里使用了render函數(shù)來(lái)構(gòu)建,這是因?yàn)閠emplate只有在完整版的Vue中才能使用。

這樣似乎還不錯(cuò),但是還有一個(gè)重要的問(wèn)題,在Vue組件中是可以使用插槽的。

比如:

<template>
 <div>
  <p>{{title}}</p>
  <button @click="changeTitle"></button>
  <slot></slot>
 </div>
</template>

在父組件中

<child-component :title='title' @changeTitle='changeTitle'>Hello, HOC</child-component>

可以用this.$solts訪問(wèn)到被插槽分發(fā)的內(nèi)容。每個(gè)具名插槽都有其相應(yīng)的property,例如v-slot:foo中的內(nèi)容將會(huì)在this.$slots.foo中被找到。而default property包括了所有沒(méi)有被包含在具名插槽中的節(jié)點(diǎn),或v-slot:default的內(nèi)容。

所以在使用渲染函數(shù)書(shū)寫(xiě)一個(gè)組件時(shí),訪問(wèn)this.$slots最有幫助的。

先將this.$slots轉(zhuǎn)化為數(shù)組,因?yàn)殇秩竞瘮?shù)的第三個(gè)參數(shù)是子節(jié)點(diǎn),是一個(gè)數(shù)組

export default function withComponent (WrapperComponent) {
  return {
    mounted () {
         console.log('已經(jīng)掛載完成')
    },
    props: WrappedComponent.props,
    render (h) {
      const keys = Object.keys(this.$slots);
      const slotList = keys.reduce((arr, key) => arr.concat(this.$slots[key]), []);
      return h(WrapperComponent, {
        on: this.$listeners,
        attrs: this.$attrs,
        props: this.$props
      }, slotList)
    }
  }
}

總算是有模有樣了,但這還沒(méi)結(jié)束,你會(huì)發(fā)現(xiàn)使不使用具名插槽都一樣,最后都是按默認(rèn)插槽來(lái)處理的。

有點(diǎn)納悶,去看看Vue源碼中是怎么具名插槽的。
在src/core/instance/render.js文件中找到了initRender函數(shù),在初始化render函數(shù)時(shí)

const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)

這一段代碼是Vue解析并處理slot的。
將vm.$options._parentVnode賦值為vm.$vnode,也就是$vnode就是父組件的vnode。如果父組件存在,定義renderContext = vm.$vnode.context。renderContext就是父組件要渲染的實(shí)例。 然后把renderContext和$options._renderChildren作為參數(shù)傳進(jìn)resolveSlots()函數(shù)中。

接下里看看resolveSlots()函數(shù),在src/core/instance/render-helper/resolve-slots.js文件中

export function resolveSlots (
 children: ?Array<VNode>,
 context: ?Component
): { [key: string]: Array<VNode> } {
 if (!children || !children.length) {
  return {}
 }
 const slots = {}
 for (let i = 0, l = children.length; i < l; i++) {
  const child = children[i]
  const data = child.data
  // remove slot attribute if the node is resolved as a Vue slot node
  if (data && data.attrs && data.attrs.slot) {
   delete data.attrs.slot
  }
  // named slots should only be respected if the vnode was rendered in the
  // same context.
  if ((child.context === context || child.fnContext === context) &&
   data && data.slot != null
  ) {
   const name = data.slot
   const slot = (slots[name] || (slots[name] = []))
   if (child.tag === 'template') {
    slot.push.apply(slot, child.children || [])
   } else {
    slot.push(child)
   }
  } else {
   (slots.default || (slots.default = [])).push(child)
  }
 }
 // ignore slots that contains only whitespace
 for (const name in slots) {
  if (slots[name].every(isWhitespace)) {
   delete slots[name]
  }
 }
 return slots
}

重點(diǎn)來(lái)看里面的一段if語(yǔ)句

// named slots should only be respected if the vnode was rendered in the
// same context.
if ((child.context === context || child.fnContext === context) &&
 data && data.slot != null
) {
 const name = data.slot
 const slot = (slots[name] || (slots[name] = []))
 if (child.tag === 'template') {
  slot.push.apply(slot, child.children || [])
 } else {
  slot.push(child)
 }
} else {
 (slots.default || (slots.default = [])).push(child)
}

只有當(dāng)if ((child.context === context || child.fnContext === context) && data && data.slot != null ) 為真時(shí),才處理為具名插槽,否則不管具名不具名,都當(dāng)成默認(rèn)插槽處理

else {
 (slots.default || (slots.default = [])).push(child)
}

那為什么HOC上的if條件是不成立的呢?

這是因?yàn)橛捎贖OC的介入,在原本的父組件與子組件之間插入了一個(gè)組件--也就是HOC,這導(dǎo)致了子組件中訪問(wèn)的this.$vode已經(jīng)不是原本的父組件的vnode了,而是HOC中的vnode,所以這時(shí)的this.$vnode.context引用的是高階組件,但是我們卻將slot透?jìng)髁耍瑂lot中的VNode的context引用的還是原來(lái)的父組件實(shí)例,所以就導(dǎo)致不成立。

從而都被處理為默認(rèn)插槽。

解決方法也很簡(jiǎn)單,只需手動(dòng)的將slot中的vnode的context指向?yàn)镠OC實(shí)例即可。注意當(dāng)前實(shí)例 _self 屬性訪問(wèn)當(dāng)前實(shí)例本身,而不是直接使用 this,因?yàn)?this 是一個(gè)代理對(duì)象。

export default function withComponent (WrapperComponent) {
  return {
    mounted () {
         console.log('已經(jīng)掛載完成')
    },
    props: WrappedComponent.props,
    render (h) {
      const keys = Object.keys(this.$slots);
      const slotList = keys.reduce((arr, key) => arr.concat(this.$slots[key]), []).map(vnode => {
        vnode.context = this._self
        return vnode
      });
      return h(WrapperComponent, {
        on: this.$listeners,
        attrs: this.$attrs,
        props: this.$props
      }, slotList)
    }
  }
}

而且scopeSlot與slot的處理方式是不同的,所以將scopeSlot一起透?jìng)?/p>

export default function withComponent (WrapperComponent) {
  return {
    mounted () {
         console.log('已經(jīng)掛載完成')
    },
    props: WrappedComponent.props,
    render (h) {
      const keys = Object.keys(this.$slots);
      const slotList = keys.reduce((arr, key) => arr.concat(this.$slots[key]), []).map(vnode => {
        vnode.context = this._self
        return vnode
      });
      return h(WrapperComponent, {
        on: this.$listeners,
        attrs: this.$attrs,
        props: this.$props,
        scopedSlots: this.$scopedSlots
      }, slotList)
    }
  }
}

這樣就行了。

結(jié)尾

更多文章請(qǐng)移步樓主github,如果喜歡請(qǐng)點(diǎn)一下star,對(duì)作者也是一種鼓勵(lì)。

到此這篇關(guān)于在Vue中使用HOC模式的文章就介紹到這了,更多相關(guān)Vue使用HOC模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue+elementui實(shí)現(xiàn)動(dòng)態(tài)控制表格列的顯示和隱藏

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

    這篇文章主要介紹了vue+elementui實(shí)現(xiàn)動(dòng)態(tài)控制表格列的顯示和隱藏,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • Vue實(shí)現(xiàn)當(dāng)前頁(yè)面刷新的七種方法總結(jié)

    Vue實(shí)現(xiàn)當(dāng)前頁(yè)面刷新的七種方法總結(jié)

    大家在vue項(xiàng)目中當(dāng)刪除或者增加一條記錄的時(shí)候希望當(dāng)前頁(yè)面可以重新刷新,但是vue框架自帶的router是不支持刷新當(dāng)前頁(yè)面功能,所以本文就給大家分享了七種vue實(shí)現(xiàn)當(dāng)前頁(yè)面刷新的方法,需要的朋友可以參考下
    2023-07-07
  • vue實(shí)現(xiàn)div可拖動(dòng)位置也可改變盒子大小的原理

    vue實(shí)現(xiàn)div可拖動(dòng)位置也可改變盒子大小的原理

    這篇文章主要介紹了vue實(shí)現(xiàn)div可拖動(dòng)位置也可改變盒子大小,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-09-09
  • Vue?中生命周期定義及流程

    Vue?中生命周期定義及流程

    這篇文章主要介紹了Vue中生命周期定義及流程,生命周期又被稱為生命周期回調(diào)函數(shù)、生命周期函數(shù)或生命周期鉤子,下文關(guān)于其更多有趣的介紹需要的小伙伴可以參考一下文章詳細(xì)內(nèi)容
    2022-05-05
  • 如何解決.vue文件url引用文件的問(wèn)題

    如何解決.vue文件url引用文件的問(wèn)題

    這篇文章主要介紹了解決.vue文件url引用文件的問(wèn)題,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-01-01
  • 詳解Vue如何將多個(gè)空格被合并顯示成一個(gè)空格

    詳解Vue如何將多個(gè)空格被合并顯示成一個(gè)空格

    這篇文章主要為大家詳細(xì)介紹了在Vue中如何將多個(gè)空格被合并顯示成一個(gè)空格,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-04-04
  • vue3+ts項(xiàng)目搭建的實(shí)現(xiàn)示例

    vue3+ts項(xiàng)目搭建的實(shí)現(xiàn)示例

    這篇文章主要介紹了vue3+ts項(xiàng)目搭建的實(shí)現(xiàn)示例,本文目的在于記錄自己項(xiàng)目框架搭建的過(guò)程,通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-03-03
  • vue中axios的封裝問(wèn)題(簡(jiǎn)易版攔截,get,post)

    vue中axios的封裝問(wèn)題(簡(jiǎn)易版攔截,get,post)

    這篇文章主要介紹了vue中axios的封裝問(wèn)題(簡(jiǎn)易版攔截,get,post),需要的朋友可以參考下
    2018-06-06
  • vue echarts實(shí)現(xiàn)綁定事件和解綁事件

    vue echarts實(shí)現(xiàn)綁定事件和解綁事件

    這篇文章主要介紹了vue echarts實(shí)現(xiàn)綁定事件和解綁事件方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • 使用Vue.js實(shí)現(xiàn)一個(gè)循環(huán)倒計(jì)時(shí)功能

    使用Vue.js實(shí)現(xiàn)一個(gè)循環(huán)倒計(jì)時(shí)功能

    在Web應(yīng)用中,倒計(jì)時(shí)功能常用于各種場(chǎng)景,如活動(dòng)倒計(jì)時(shí)、定時(shí)任務(wù)提醒等,Vue.js作為一款輕量級(jí)的前端框架,提供了豐富的工具和API來(lái)實(shí)現(xiàn)這些功能,本文將詳細(xì)介紹如何使用Vue.js實(shí)現(xiàn)一個(gè)循環(huán)倒計(jì)時(shí)功能,需要的朋友可以參考下
    2024-09-09

最新評(píng)論

红桥区| 土默特右旗| 江油市| 保定市| 白城市| 高邮市| 买车| 华宁县| 扎囊县| 南陵县| 托克逊县| 汶川县| 德惠市| 隆昌县| 清水河县| 巴塘县| 鸡西市| 达州市| 泊头市| 利津县| 精河县| 陈巴尔虎旗| 麻栗坡县| 白城市| 新郑市| 西昌市| 伽师县| 宝丰县| 黄平县| 和田县| 晋中市| 嘉善县| 汝阳县| 双牌县| 九龙坡区| 新河县| 砀山县| 武宁县| 昭觉县| 门源| 车险|