杭州网站建设哪家最好,中企视窗做网站怎么样,婚庆公司赚钱吗,wordpress微信登录页面模板[vue] 你了解什么是高阶组件吗#xff1f;可否举个例子说明下#xff1f;
高阶组件 高阶组件介绍
vue 高阶组件的认识#xff0c;在React中组件是以复用代码实现的#xff0c;而Vue中是以mixins 实现#xff0c;并且官方文档中也缺少一些高阶组件的概念,因为在vue中实现…[vue] 你了解什么是高阶组件吗可否举个例子说明下
高阶组件 高阶组件介绍
vue 高阶组件的认识在React中组件是以复用代码实现的而Vue中是以mixins 实现并且官方文档中也缺少一些高阶组件的概念,因为在vue中实现高阶组很困难并不像React简单,其实vue中mixins也同样和以代替,在读了一部分源码之后,对vue有了更深的认识
所谓高阶组件其实就是一个高阶函数, 即返回一个组件函数的函数Vue中怎么实现呢 注意 高阶组件有如下特点
高阶组件(HOC)应该是无副作用的纯函数且不应该修改原组件,即原组件不能有变动
高阶组件(HOC)不关心你传递的数据(props)是什么并且新生成组件不关心数据来源
高阶组件(HOC)接收到的 props 应该透传给被包装组件即直接将原组件prop传给包装组件
高阶组件完全可以添加、删除、修改 props高阶组件举例
Base.vue
templatedivp clickClickprops: {{test}}/p/div
/template
script
export default {name: Base,props: {test: Number},methods: {Click () {this.$emit(Base-click)}}
}
/scriptVue 组件主要就是三点props、event 以及 slots。对于 Base组件 组件而言它接收一个数字类型的 props 即 test并触发一个自定义事件事件的名称是Base-click没有 slots。我们会这样使用该组件
现在我们需要 base-component 组件每次挂载完成的时候都打印一句话haha同时这也许是很多组件的需求所以按照 mixins 的方式我们可以这样做首先定义个 mixins
export default consoleMixin {mounted () {console.log(haha)}
}然后在 Base 组件中将 consoleMixin 混入
templatedivp clickClickprops: {{test}}/p/div
/template
script
export default {name: Base,props: {test: Number},mixins: [ consoleMixin ],methods: {Click () {this.$emit(Base-click)}}
}
/script这样使用 Base 组件的时候每次挂载完成之后都会打印一句 haha不过现在我们要使用高阶组件的方式实现同样的功能回忆高阶组件的定义接收一个组件作为参数返回一个新的组件那么此时我们需要思考的是在 Vue 中组件是什么Vue 中组件是函数不过那是最终结果比如我们在单文件组件中的组件定义其实就是一个普通的选项对象如下
export default {name: Base,props: {...},mixins: [...]methods: {...}
}这难道不是一个纯对象嘛
import Base from ./Base.vue
console.log(Base)这里的Base是什么呢 对就是一个JSON对象,而当以把他加入到一个组件的componentsVu最终会以该参数即option来构造实例的构造函数所以Vue中组件就是个函数但是在引入之前仍只是一个options对象,所以这样就很好明白了 Vue中组件开始只是一个对象即高阶组件就是 一个函数接受一个纯对象并且返回一个新纯对象
export default function Console (BaseComponent) {return {template: wrapped v-on$listeners v-bind$attrs/,components: {wrapped: BaseComponent},mounted () {console.log(haha)}}
}这里 Console就是一个高阶组件它接受一个参数 BaseComponent即传入的组件,返回一个新组件,将BaseComponent作为新组件的子组件并且在mounted里设置钩子函数 打印haha,我们可以完成mixins同样做到的事,我们并没有修改子组件Base,这里的 $listeners $attrs 其实是在透传props 和事件 那这样真的就完美解决问题了吗不是的首先 template 选项只有在完整版的 Vue 中可以使用在运行时版本中是不能使用的所以最起码我们应该使用渲染函数(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,})}}
}我们将模板改写成了渲染函数看上去没什么问题实际还是有问题上面的代码中 BaseComponent 组件依然收不到 props为什么呢我们不是已经在 h 函数的第二个参数中将 attrs 传递过去了吗怎么还收不到当然收不到attrs 指的是那些没有被声明为 props 的属性所以在渲染函数中还需要添加 props 参数
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 一个差不多的高阶组件就完成了 但是能还每完 我们只实现了 透传props,透传事件,emmmm就剩下slot了 我们修改 Base 组件为其添加一个具名插槽和默认插槽 Base.vue
templatedivspan clickhandleClickprops: {{test}}/spanslot nameslot1/ !-- 具名插槽 --/slotp/pslotslot/ !-- 默认插槽 --/div
/templatescript
export default {...
}
/scripttemplatedivBaseh2 slotslot1BaseComponent slot/h2pdefault slot/p/BasewrapBaseh2 slotslot1EnhancedComponent slot/h2pdefault slot/p/wrapBase/div
/templatescriptimport Base from ./Base.vueimport hoc from ./Console.jsconst wrapBase Console(Base)export default {components: {Base,wrapBase}}
/script这里的执行结果就是 wrapBase里的slot都没有了 所以就要改一下高阶组建了
function Console (BaseComponent) {return {mounted () {console.log(haha)},props: BaseComponent.props,render (h) {// 将 this.$slots 格式化为数组因为 h 函数第三个参数是子节点是一个数组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 函数的第三个参数}}
}这时 slot内容确实渲染出来了 但是顺序不太对 高阶组件的全部渲染到了末尾。。 其实 Vue在处理具名插槽会考虑作用域的因素 首先 Vue 会把模板(template)编译成渲染函数(render)比如如下模板
divp slotslot1Base slot/p
/div会被编译成如下渲染函数
var render function() {var _vm thisvar _h _vm.$createElementvar _c _vm._self._c || _hreturn _c(div, [_c(div, {attrs: { slot: slot1 },slot: slot1}, [_vm._v(Base slot)])])
}观察上面的渲染函数我们发现普通的 DOM 是通过 _c 函数创建对应的 VNode 的。现在我们修改模板模板中除了有普通 DOM 之外还有组件如下
divBasep slotslot1Base slot/ppdefault slot/p/Base
/div其render函数
var render function() {var _vm thisvar _h _vm.$createElementvar _c _vm._self._c || _hreturn _c(div,[_c(Base, [_c(p, { attrs: { slot: slot1 }, slot: slot1 }, [_vm._v(Base slot)]),_vm._v( ),_c(p, [_vm._v(default slot)])])],)
}我们发现无论是普通DOM还是组件都是通过 _c 函数创建其对应的 VNode 的 其实 _c 在 Vue 内部就是 createElement 函数。createElement 函数会自动检测第一个参数是不是普通DOM标签如果不是普通DOM标签那么 createElement 会将其视为组件并且创建组件实例,注意组件实例是这个时候才创建的 但是创建组件实例的过程中就面临一个问题组件需要知道父级模板中是否传递了 slot 以及传递了多少传递的是具名的还是不具名的等等。那么子组件如何才能得知这些信息呢很简单假如组件的模板如下
divBasep slotslot1Base slot/ppdefault slot/p/Base
/div父组件的模板最终会生成父组件对应的 VNode所以以上模板对应的 VNode 全部由父组件所有那么在创建子组件实例的时候能否通过获取父组件的 VNode 进而拿到 slot 的内容呢即通过父组件将下面这段模板对应的 VNode 拿到
Basep slotslot1Base slot/ppdefault slot/p/Base如果能够通过父级拿到这段模板对应的 VNode那么子组件就知道要渲染哪些 slot 了其实 Vue 内部就是这么干的实际上你可以通过访问子组件的 this.$vnode 来获取这段模板对应的 VNode
this.$vnode 并没有写进 Vue 的官方文档
子组件拿到了需要渲染的 slot 之后进入到了关键的一步这一步就是导致高阶组件中透传 slot 给 Base组件 却无法正确渲染的原因 children的VNode中的context引用父组件实例 其本身的context也会引用本身实例 其实是一个东西
console.log(this.vnode.contextthis.vnode.context this.vnode.contextthis.vnode.componentOptions.children[0].context) //ture
而 Vue 内部做了一件很重要的事儿即上面那个表达式必须成立才能够正确处理具名 slot否则即使 slot 具名也不会被考虑而是被作为默认插槽。这就是高阶组件中不能正确渲染 slot 的原因
即 高阶组件中 本来时父组件和子组件之间插入了一个组件(高阶组件),而子组件的 this.$vnode其实是高阶组件的实例但是我们将slot透传给子组件,slot里 VNode 的context实际引用的还是父组件 所以
console.log(this.vnode.contextthis.vnode.context this.vnode.contextthis.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的归属权给高阶组件 而不是 父组件 通过当前实例 _self 属性访问当实例本身而不是直接使用 this因为 this 是一个代理对象
个人简介
我是歌谣欢迎和大家一起交流前后端知识。放弃很容易 但坚持一定很酷。欢迎大家一起讨论
主目录
与歌谣一起通关前端面试题