In vue, a high-order component is actually a high-order function, that is, a function that returns a component function. Characteristics of high-order components: 1. It is a pure function with no side effects, and the original component should not be modified, that is, the original component cannot be changed; 2. It does not care about the data (props) passed, and the newly generated component does not care about the source of the data. ; 3. The received props should be passed to the packaged component, that is, the original component props are directly passed to the packaging component; 4. High-order components can completely add, delete, and modify props.
The operating environment of this tutorial: windows7 system, vue3 version, DELL G3 computer.
Introduction to high-order components
vue Understanding of high-order components, in React components are implemented with reused code, while in Vue they are implemented with mixins, and The official documentation also lacks some concepts of high-order components, because it is difficult to implement high-order components in Vue and is not as simple as React. In fact, mixins in Vue are also replaced by mixins. After reading part of the source code, I have a deeper understanding of Vue. Understanding
The so-called high-order component is actually a high-order function, that is, a function that returns a component function. How to implement it in Vue? Note that high-order components have the following characteristics
高階組件(HOC)應(yīng)該是無副作用的純函數(shù),且不應(yīng)該修改原組件,即原組件不能有變動 高階組件(HOC)不關(guān)心你傳遞的數(shù)據(jù)(props)是什么,并且新生成組件不關(guān)心數(shù)據(jù)來源 高階組件(HOC)接收到的?props?應(yīng)該傳遞給被包裝組件即直接將原組件prop傳給包裝組件 高階組件完全可以添加、刪除、修改?props
Examples of high-order components
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 components mainly have three points: props, events and slots. For the Base component component, it receives a numeric type props, namely test, and triggers a custom event. The name of the event is: Base-click, without slots. We will use the component like this:
Now we need the base-component component to print a sentence: haha ??every time it is mounted. At the same time, this may be a requirement of many components, so according to the mixins method, we You can do this, first define mixins
export?default?consoleMixin?{ ??mounted?()?{ ????console.log('haha') ??} }
and then mix consoleMixin in the Base component:
<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>
When using the Base component in this way, a haha ??message will be printed after each mounting is completed, but Now we are going to use higher-order components to achieve the same function. Recall the definition of higher-order components: receive a component as a parameter and return a new component. So what we need to think about at this time is, what is a component in Vue? Components in Vue are functions, but that is the final result. For example, our component definition in a single-file component is actually an ordinary options object, as follows:
export?default?{ ??name:?'Base', ??props:?{...}, ??mixins:?[...] ??methods:?{...} }
Isn’t this a pure object?
import?Base?from?'./Base.vue' console.log(Base)
What is Base here? It is a JSON object. When you add it to the components of a component, Vu will eventually construct the constructor of the instance with this parameter, that is, option, so the component in Vue is a function. But before it is introduced, it is still just an options object, so it is easy to understand that the component in Vue is just an object at first, that is, the higher-order component is a function that accepts a pure object and returns a new pure object
export?default?function?Console?(BaseComponent)?{ ??return?{ ????template:?'<wrapped v-on="$listeners" v-bind="$attrs"/>', ????components:?{ ??????wrapped:?BaseComponent ????}, ????mounted?()?{ ??????console.log('haha') ????} ??} }
Console here is a higher-order component. It accepts a parameter BaseComponent, which is the passed in component, returns a new component, uses BaseComponent as a subcomponent of the new component and sets a hook function in mounted to print haha. We can do the same with mixins. The thing is, we have not modified the sub-component Base. The $listeners $attrs here are actually transparently transmitting props and events. Does this really solve the problem perfectly? No, first of all, the template option can only be used in the full version of Vue, not in the runtime version, so at the very least we should use the render function (render) instead of the template (template)
Console. js
export?default?function?Console?(BaseComponent)?{ ??return?{ ????mounted?()?{ ??????console.log('haha') ????}, ????render?(h)?{ ??????return?h(BaseComponent,?{ ????????on:?this.$listeners, ????????attrs:?this.$attrs, ??????}) ????} ??} }
We rewrote the template into a rendering function. It seems that there is no problem, but in fact there is still a problem. In the above code, the BaseComponent component still cannot receive props. Why, aren't we already in the h function? Have you passed attrs among the two parameters? Why can't you receive it? Of course you can't receive it. Attrs refers to attributes that have not been declared as props, so you need to add props parameters in the rendering function:
export?default?function?Console?(BaseComponent)?{ ??return?{ ????mounted?()?{ ??????console.log('haha') ????}, ????render?(h)?{ ??????return?h(BaseComponent,?{ ????????on:?this.$listeners, ????????attrs:?this.$attrs, ????????props:?this.$props ??????}) ????} ??} }
Then it still doesn't work. Props are always empty objects. Here props is the object of the higher-order component, but the higher-order component does not declare props, so we need to declare another 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. A similar higher-order component is completed, but if it is still possible, we only implement it. Transparently transmitting props and transparently transmitting events, emmmm only slots are left. We modify the Base component to add a named slot and a default slot Base.vue
<template> ??<div> ????<span @click="handleClick">props:?{{test}}</span> ????<slot name="slot1"/>?<!-- 具名插槽 --></slot> ????<p>===========</p> ????<slot><slot/>?<!-- 默認(rèn)插槽 --> ??</div> </template> ? <script> export?default?{ ??... } </script> <template> ??<div> ????<Base> ??????<h2 slot="slot1">BaseComponent?slot</h2> ??????<p>default?slot</p> ????</Base> ????<wrapBase> ??????<h2 slot="slot1">EnhancedComponent?slot</h2> ??????<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>
The execution result here is that there are no slots in wrapBase. So we have to change the high-level components
function?Console?(BaseComponent)?{ ??return?{ ????mounted?()?{ ??????console.log('haha') ????}, ????props:?BaseComponent.props, ????render?(h)?{ ? ??????//?將?this.$slots?格式化為數(shù)組,因?yàn)?h?函數(shù)第三個參數(shù)是子節(jié)點(diǎn),是一個數(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ù) ????} ??} }
At this time, the slot content is indeed rendered, but the order is not right, and all high-order components are rendered to the end. . In fact, Vue will consider scope factors when processing named slots. First, Vue will compile the template into a rendering function (render). For example, the following template:
<div> ??<p slot="slot1">Base?slot</p> </div>
will be compiled into the following rendering function:
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") ????]) ??]) }
Observing the above rendering function, we find that the ordinary DOM creates the corresponding VNode through the _c function. Now we modify the template. In addition to the ordinary DOM, the template also has components, as follows:
<div> ??<Base> ????<p slot="slot1">Base?slot</p> ????<p>default?slot</p> ??</Base> </div>
its render function
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)建其對應(yīng)的 VNode 的 其實(shí) _c 在 Vue 內(nèi)部就是 createElement 函數(shù)。createElement 函數(shù)會自動檢測第一個參數(shù)是不是普通DOM標(biāo)簽如果不是普通DOM標(biāo)簽?zāi)敲?createElement 會將其視為組件,并且創(chuàng)建組件實(shí)例,注意組件實(shí)例是這個時候才創(chuàng)建的 但是創(chuàng)建組件實(shí)例的過程中就面臨一個問題:組件需要知道父級模板中是否傳遞了 slot 以及傳遞了多少,傳遞的是具名的還是不具名的等等。那么子組件如何才能得知這些信息呢?很簡單,假如組件的模板如下
<div> ??<Base> ????<p slot="slot1">Base?slot</p> ????<p>default?slot</p> ??</Base> </div>
父組件的模板最終會生成父組件對應(yīng)的 VNode,所以以上模板對應(yīng)的 VNode 全部由父組件所有,那么在創(chuàng)建子組件實(shí)例的時候能否通過獲取父組件的 VNode 進(jìn)而拿到 slot 的內(nèi)容呢?即通過父組件將下面這段模板對應(yīng)的 VNode 拿到
<Base> ????<p slot="slot1">Base?slot</p> ????<p>default?slot</p> ??</Base>
如果能夠通過父級拿到這段模板對應(yīng)的 VNode,那么子組件就知道要渲染哪些 slot 了,其實(shí) Vue 內(nèi)部就是這么干的,實(shí)際上你可以通過訪問子組件的 this.$vnode 來獲取這段模板對應(yīng)的 VNode
this.$vnode 并沒有寫進(jìn) Vue 的官方文檔
子組件拿到了需要渲染的 slot 之后進(jìn)入到了關(guān)鍵的一步,這一步就是導(dǎo)致高階組件中透傳 slot 給 Base組件 卻無法正確渲染的原因 children的VNode中的context引用父組件實(shí)例 其本身的context也會引用本身實(shí)例 其實(shí)是一個東西
console.log(this.?vnode.context===this.vnode.componentOptions.children[0].context) //ture
而 Vue 內(nèi)部做了一件很重要的事兒,即上面那個表達(dá)式必須成立,才能夠正確處理具名 slot,否則即使 slot 具名也不會被考慮,而是被作為默認(rèn)插槽。這就是高階組件中不能正確渲染 slot 的原因
即 高階組件中 本來時父組件和子組件之間插入了一個組件(高階組件),而子組件的 this.$vnode其實(shí)是高階組件的實(shí)例,但是我們將slot透傳給子組件,slot里 VNode 的context實(shí)際引用的還是父組件 所以
console.log(this.vnode.context === this.vnode.componentOptions.children[0].context) // false
最終導(dǎo)致具名插槽被作為默認(rèn)插槽,從而渲染不正確。
決辦法也很簡單,只需要手動設(shè)置一下 slot 中 VNode 的 context 值為高階組件實(shí)例即可
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) } } }
說明白就是強(qiáng)制把slot的歸屬權(quán)給高階組件 而不是 父組件 通過當(dāng)前實(shí)例 _self 屬性訪問當(dāng)實(shí)例本身,而不是直接使用 this,因?yàn)?this 是一個代理對象
【相關(guān)推薦:vuejs視頻教程、web前端開發(fā)】
The above is the detailed content of What are vue high-order components?. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

ToimplementdarkmodeinCSSeffectively,useCSSvariablesforthemecolors,detectsystempreferenceswithprefers-color-scheme,addamanualtogglebutton,andhandleimagesandbackgroundsthoughtfully.1.DefineCSSvariablesforlightanddarkthemestomanagecolorsefficiently.2.Us

The topic differencebetweenem, Rem, PX, andViewportunits (VH, VW) LiesintheirreFerencepoint: PXISFixedandbasedonpixelvalues, emissrelative EtothefontsizeFheelementoritsparent, Remisrelelatotherootfontsize, AndVH/VwarebaseDontheviewporttimensions.1.PXoffersprecis

CSSHoudini is a set of APIs that allow developers to directly manipulate and extend the browser's style processing flow through JavaScript. 1. PaintWorklet controls element drawing; 2. LayoutWorklet custom layout logic; 3. AnimationWorklet implements high-performance animation; 4. Parser&TypedOM efficiently operates CSS properties; 5. Properties&ValuesAPI registers custom properties; 6. FontMetricsAPI obtains font information. It allows developers to expand CSS in unprecedented ways, achieve effects such as wave backgrounds, and have good performance and flexibility

Choosing the correct display value in CSS is crucial because it controls the behavior of elements in the layout. 1.inline: Make elements flow like text, without occupying a single line, and cannot directly set width and height, suitable for elements in text, such as; 2.block: Make elements exclusively occupy one line and occupy all width, can set width and height and inner and outer margins, suitable for structured elements, such as; 3.inline-block: has both block characteristics and inline layout, can set size but still display in the same line, suitable for horizontal layouts that require consistent spacing; 4.flex: Modern layout mode, suitable for containers, easy to achieve alignment and distribution through justify-content, align-items and other attributes, yes

ReactivitytransforminVue3aimedtosimplifyhandlingreactivedatabyautomaticallytrackingandmanagingreactivitywithoutrequiringmanualref()or.valueusage.Itsoughttoreduceboilerplateandimprovecodereadabilitybytreatingvariableslikeletandconstasautomaticallyreac

CSSgradientsenhancebackgroundswithdepthandvisualappeal.1.Startwithlineargradientsforsmoothcolortransitionsalongaline,specifyingdirectionandcolorstops.2.Useradialgradientsforcirculareffects,adjustingshapeandcenterposition.3.Layermultiplegradientstocre

InternationalizationandlocalizationinVueappsareprimarilyhandledusingtheVueI18nplugin.1.Installvue-i18nvianpmoryarn.2.CreatelocaleJSONfiles(e.g.,en.json,es.json)fortranslationmessages.3.Setupthei18ninstanceinmain.jswithlocaleconfigurationandmessagefil

In Vue, provide and inject are features for directly passing data across hierarchical components. The parent component provides data or methods through provide, and descendant components directly inject and use these data or methods through inject, without passing props layer by layer; 2. It is suitable for avoiding "propdrilling", such as passing global or shared data such as topics, user status, API services, etc.; 3. Note when using: non-responsive original values ??must be wrapped into responsive objects to achieve responsive updates, and should not be abused to avoid affecting maintainability.
