[vue] 你了解什么是高阶组件吗?可否举个例子说明下?

[vue] 你了解什么是高阶组件吗?可否举个例子说明下?

高阶组件
高阶组件介绍

vue 高阶组件的认识,在React中组件是以复用代码实现的,而Vue中是以mixins 实现,并且官方文档中也缺少一些高阶组件的概念,因为在vue中实现高阶组很困难,并不像React简单,其实vue中mixins也同样和以代替,在读了一部分源码之后,对vue有了更深的认识

所谓高阶组件其实就是一个高阶函数, 即返回一个组件函数的函数,Vue中怎么实现呢? 注意 高阶组件有如下特点

高阶组件(HOC)应该是无副作用的纯函数,且不应该修改原组件,即原组件不能有变动
高阶组件(HOC)不关心你传递的数据(props)是什么,并且新生成组件不关心数据来源
高阶组件(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组件 组件而言,它接收一个数字类型的 props 即 test,并触发一个自定义事件,事件的名称是:Base-click,没有 slots。我们会这样使用该组件:

现在我们需要 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,不过现在我们要使用高阶组件的方式实现同样的功能,回忆高阶组件的定义:接收一个组件作为参数,返回一个新的组件,那么此时我们需要思考的是,在 Vue 中组件是什么?Vue 中组件是函数,不过那是最终结果,比如我们在单文件组件中的组件定义其实就是一个普通的选项对象,如下:

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

这难道不是一个纯对象嘛

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

这里的Base是什么呢 对就是一个JSON对象,而当以把他加入到一个组件的components,Vu最终会以该参数即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

<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><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>

这里的执行结果就是 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),比如如下模板:

<div><p slot="slot1">Base 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 之外,还有组件,如下:

<div><Base><p slot="slot1">Base slot</p><p>default 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 以及传递了多少,传递的是具名的还是不具名的等等。那么子组件如何才能得知这些信息呢?很简单,假如组件的模板如下

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

父组件的模板最终会生成父组件对应的 VNode,所以以上模板对应的 VNode 全部由父组件所有,那么在创建子组件实例的时候能否通过获取父组件的 VNode 进而拿到 slot 的内容呢?即通过父组件将下面这段模板对应的 VNode 拿到

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

如果能够通过父级拿到这段模板对应的 VNode,那么子组件就知道要渲染哪些 slot 了,其实 Vue 内部就是这么干的,实际上你可以通过访问子组件的 this.$vnode 来获取这段模板对应的 VNode

this.$vnode 并没有写进 Vue 的官方文档

子组件拿到了需要渲染的 slot 之后进入到了关键的一步,这一步就是导致高阶组件中透传 slot 给 Base组件 却无法正确渲染的原因 children的VNode中的context引用父组件实例 其本身的context也会引用本身实例 其实是一个东西

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

而 Vue 内部做了一件很重要的事儿,即上面那个表达式必须成立,才能够正确处理具名 slot,否则即使 slot 具名也不会被考虑,而是被作为默认插槽。这就是高阶组件中不能正确渲染 slot 的原因

即 高阶组件中 本来时父组件和子组件之间插入了一个组件(高阶组件),而子组件的 this.$vnode其实是高阶组件的实例,但是我们将slot透传给子组件,slot里 VNode 的context实际引用的还是父组件 所以

console.log(this.vnode.context===this.vnode.context === 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的归属权给高阶组件 而不是 父组件 通过当前实例 _self 属性访问当实例本身,而不是直接使用 this,因为 this 是一个代理对象

个人简介

我是歌谣,欢迎和大家一起交流前后端知识。放弃很容易,
但坚持一定很酷。欢迎大家一起讨论

主目录

与歌谣一起通关前端面试题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/412355.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

修改Tomcat端口号

8080是Tomcat服务器的默认端口号。我们可以通过修改Tomcat/conf文件夹下的主配置文件server.xml来更改端口号。用记事本打开server.xml文件&#xff0c;找出出现以下代码的部分&#xff1a; <!-- A "Connector" represents an endpoint by which requests are rec…

序列化与反序列化的简单认识

把对象转换为字节序列的过程称为对象的序列化。  把字节序列恢复为对象的过程称为对象的反序列化。  对象的序列化主要有两种用途&#xff1a;  1&#xff09; 把对象的字节序列永久地保存到硬盘上&#xff0c;通常存放在一个文件中&#xff1b;  2&#xff09; 在网络…

[vue] vue怎么缓存当前的组件?缓存后怎么更新?

[vue] vue怎么缓存当前的组件&#xff1f;缓存后怎么更新&#xff1f; keep-alive 通过actived钩子个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 与歌谣一起通关前端面试题

添加多个tomcat服务目录

tomcat默认的web服务的根目录为Tomcat 6.0\webapps\Root 如果将JSP文件保存至Root目录中&#xff0c;应当在浏览器的地址栏中输入&#xff1a; http://localhost:8080/MyJsp.jsp 我们也可以建立新的Web服务目录。假设要将c:\TEMP作为服务目录&#xff0c;并让用户使用temp虚…

JMeter 性能测试实例

一、性能测试分类&#xff1a; 1、基准测试 2、并发测试 3、负载测试 4、压力测试 1、基准测试&#xff1a; 也是单用户测试&#xff0c;测试环境确定以后&#xff0c;对业务模型中的重要业务做单独的测试&#xff0c;获取单用户运行时的各项性能指标&#xff0c;为多用户并发测…

[vue] vue和微信小程序写法上有什么区别?

[vue] vue和微信小程序写法上有什么区别&#xff1f;写了vue项目和小程序&#xff0c;发现二者有许多相同之处&#xff0c;在此想总结一下二者的共同点和区别。 一、生命周期 先贴两张图&#xff1a; vue生命周期 小程序生命周期 相比之下&#xff0c;小程序的钩子函数要简…

java 转换url中文参数

当使用request对象获取用户提交的汉字字符时&#xff0c;会出现乱码问题&#xff0c;所以对含有汉子字符的信息必须进行特殊的处理。 首先&#xff0c;将获取的字符串用IOS-8859-1进行编码&#xff0c;并将编码存放到一个字节数组中&#xff0c;然后再将这个数组转换为字符串对…

[vue] vue开发过程中你有使用什么辅助工具吗?

[vue] vue开发过程中你有使用什么辅助工具吗&#xff1f; #335 vue-devtools个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 与歌谣一起通关前端面试题

Django学习之十一:真正理解Django的路由分发和反解url原理

目录 URL Dispatcher简介模式概念对比URLPattern 与 URLResolver (多态的体现)构建子路由几种方式反解url算法逻辑URL Dispatcher 简介 django的url dispatcher 设计是基于一个url mapper来工作的。 这个url mapper主要用在两个方向&#xff1a; url 匹配到 视图通过提供的标识…

Unable to locate tools.jar

初使用ant的时候&#xff0c;打开cmd&#xff0c;使用ant -version查看ant版本以测试ant是否能正常工作&#xff0c; 我先前是已经将ant的bin目录添加进入环境变量中了&#xff0c;后来运行中报了这么一个错误&#xff1a; 解决办法就是将C:\Program Files (x86)\Java\jdk1.6.…

[vue] 你们项目为什么会选vue而不选择其它的框架呢?

[vue] 你们项目为什么会选vue而不选择其它的框架呢&#xff1f; Vue.js是一个轻巧、高性能、可组件化的MVVM库&#xff0c;同时拥有非常容易上手的API&#xff1b;vue是单页面应用&#xff0c;使页面局部刷新&#xff0c;不用每次跳转页面都要请求所有数据和dom&#xff0c;这…

你所忽略的,覆盖equals时需要注意的事项《effective java》

我们都知道Object的equals的比较其实就是的比较&#xff0c;其实是内存中的存放地址的比较。正常逻辑上&#xff1a;类的每个实例本质上都是唯一的。 在工作中我们实际的业务逻辑往往有可能出现一些相对特殊的需求需要对equals方法进行重写&#xff0c;那么重写equals需要注意哪…

[vue] vue在开发过程中要同时跟N个不同的后端人员联调接口(请求的url不一样)时你该怎么办?

[vue] vue在开发过程中要同时跟N个不同的后端人员联调接口&#xff08;请求的url不一样&#xff09;时你该怎么办&#xff1f; devServer中把所有的服务人员的地址代理都写进去&#xff0c; 然后动态更改接口的baseUrl&#xff0c;这样切换不同后端人员的时候不用重启个人简介…

使用 bat 文件管理计算机服务

echo off title 计算机服务管理 :allstart cls echo 曾俊工作室 echo 1.SQL Server 2008 服务开启、关闭 echo 2.MySQL 服务开启、关闭 echo 3.Oracle 11g 服务开启、关闭 echo e.退出 set in set /p in请输入: if "%in%""1" goto sqlserver if "…

处女座与复读机

链接&#xff1a;https://ac.nowcoder.com/acm/contest/327/G来源&#xff1a;牛客网 一天&#xff0c;处女座在牛客算法群里发了一句“我好强啊”&#xff0c;引起无数的复读&#xff0c;可是处女座发现复读之后变成了“处女座好强啊”。处女座经过调查发现群里的复读机都是失…

[vue] 如何解决vue打包vendor过大的问题?

[vue] 如何解决vue打包vendor过大的问题&#xff1f; 1、在webpack.base.conf.js新增externals配置&#xff0c;表示不需要打包的文件&#xff0c;然后在index.html中通过CDN引入externals: {"vue": "Vue","vue-router": "VueRouter"…

jquery ajax 解决跨域访问问题

当使用jquery ajax进行跨域请求时&#xff0c;会出现Access-Control-Allow-Origin错误 //获取验证码 var send_status true; $(#pull_code).click(function () {if (!send_status) {return false;}var phone $(#phone).val();if (!phone) {alert(请输入手机号码!);return fa…

测试网络

|||||106.13.4.129|||||转载于:https://www.cnblogs.com/Sendige/p/10343124.html

bootstrap 一排5个_BootStrap从基础到项目实战_第1季_03章_02_CSS样式栅格系统实例

目标目标一、理解什么是栅格布局目标二、掌握栅格布局具体应用目标三、掌握BootStrap通用CSS样式(排版、代码、代码、表单、按钮、图片、辅助类、响应式工具)内容一、BootStrap全局CSS之 - 栅格系统实例1.1 栅格系统实例实战前的理论准备通过下面的截图可以比较清楚的来查看Boo…

[vue] 在移动端使用vue,你觉得最佳实践有哪些?

[vue] 在移动端使用vue&#xff0c;你觉得最佳实践有哪些&#xff1f; vant&#xff0c;mint&#xff0c;uniapp个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 与歌谣一起通关前端面试题