全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2025-08-05_跟着 Vue团队大佬学习在 Vue3 中二次封装组件

您的位置:首页 >> 新闻 >> 行业资讯

跟着 Vue团队大佬学习在 Vue3 中二次封装组件 (??金石瓜分计划强势上线,速戳上图了解详情??)参考视频:《组件二次封装时不一样的插槽传递方式》 B 站视频 - 远方os《组件二次封装 - 终极版》 B 站视频 - 远方os《h 函数的使用》 B 站视频 - 远方os《h 函数的使用场景》 B 站视频 - 远方os从二次封装 el-input 开始 ??要解决的问题props 如何穿透出去?slots 如何穿透出去 ?组件的方法如何暴露出去 ?如何让 ts 类型完备 ?处理 props传递 $attrs为了保证组件原有「属性」和事件能被正常传递,我们可以使用mergeProps合并 $attrs 和重写默认属性或新增props对象,绑定到原有组件: scriptsetuplang="ts"import{ mergeProps }from'vue'importtype {ExtractPublicPropTypes}from"vue"importtype {InputProps}from'element-plus'constprops = definePropsExtractPublicPropTypesInputProps()/script template!-- el-input v-bind="{...$attrs, ...props}"/el-input --el-inputv-bind="mergeProps($attrs, props)"/el-input/template$attrs包含所有传入的 props 和 emit 事件;直接使用$attrs是没有 TS 类型提示的,所以我们要声明一个props,至于 props 类型一般组件库都会导出。「?? ExtractPublicPropTypes 类型是什么作用 ?」 其实,在上面的代码中,对于 props 的 TS 类型定义,一开始其实我使用的是PartialInputProps,把所有属性都变成可选,这样父组件使用时类型提示才不会报错,但是这样并不严谨,如果组件里有 props 属性是必填的,那可能不会有完备的类型提示。 对于 TypeScript 如果需要获取 Props 的类型,那就需要用到 Vue 的一个辅助类型ExtractPropTypes,而在 element-plus 源码中, 大部分组件的 props 是用ExtractPropTypestypeof inputProps抽离的。 这里是我们属于二次封装组件,所以我们是外部引用 (父组件),对于外部引用,我们就使用ExtractPublicPropTypes。 「参考链接」 『精』Vue 组件如何模块化抽离PropsVue 官方文档 - TypeScript 工具类型 - ExtractPublicPropTypes覆盖默认值我们可以使用withDefaults给 props 设置默认值,从而达到覆盖原组件默认值的效果 script setup lang="ts"import{ mergeProps }from'vue'importtype{ExtractPublicPropTypes}from"vue"importtype{InputPropsasElInputProps}from'element-plus' typeInputProps=ExtractPublicPropTypesElInputProps & {/* 可以在此处添加新属性 */} constprops =withDefaults(definePropsInputProps(), {clearable:true,// 改变el-input clearable 默认值 /* 可以在此处为新属性添加默认值 */})/script templateel-inputv-bind="mergeProps($attrs, props)"/el-input/template处理 slots常规版本我们以 element-plusInput 输入框[1]组件为例,为了向子组件传递插槽,常规的做法 , 遍历$slots来实现,不论是封装什么组件都可以无脑使用v-for v-for="(_, name) in $slots",即使组件插槽相互有逻辑也不会被影响。 script setup lang="ts"import{ mergeProps }from'vue'importtype{ExtractPublicPropTypes}from"vue"importtype{InputPropsasElInputProps}from'element-plus' typeInputProps=ExtractPublicPropTypesElInputProps & {}constprops =withDefaults(definePropsInputProps(),{}) /script templateel-inputv-bind="mergeProps($attrs, props)" templatev-for="(_, name) in $slots":key="name"#[name]="slotProps" slot:/slot /template/el-input/template#[name]="slotProps"等同于v-slot:[name]="slotProps"。 「关于遍历 $slot 写法问题」 $slots是个 Proxy 对象,下面的写法均可: v-for="(_, name) in $slots"v-for="(_, name) of $slots"v-for="(_, name) Object.keys($slots)"示例:在父组件使用, 并传递prepend、append插槽: template div h3?? 父组件/h3 YiInputref="inputRef"v-model="msg"placeholder="请输入内容" template#append el-iconSearch//el-icon /template template#suffix el-iconUser//el-icon /template /YiInput /div/template scriptlang="ts"setupimport{Search,User}from'@element-plus/icons-vue'importtype {InputInstance}from'element-plus' constinputRef = refInputInstance()constmsg =ref('Hello world') setTimeout(() ={ inputRef.value?.focus()// 自动聚焦},3000)/script使用 h 函数 (花活版??)script setup lang="ts"import{ElInput}from"element-plus"importtype{ExtractPublicPropTypes}from"vue"importtype{InputPropsasElInputProps}from'element-plus' typeInputProps=ExtractPublicPropTypesElInputProps & {}constprops =withDefaults(definePropsInputProps(),{})/script templatecomponent:is="h(ElInput, { ...$attrs, ...props }, $slots)"//template使用 Vue 3.3+ 新增加辅助函数 ( 花活版 ?? )在 Vue 中,我们可以在模板中直接通过$slots和$attrs来访问它们、 在 Vue 3.4 版本之后,可以分别用useSlots和useAttrs两个辅助函数: script setup lang="ts"import{ h, mergeProps, useAttrs, useSlots }from'vue'import{ElInput}from"element-plus"importtype{ExtractPublicPropTypes}from"vue"importtype{InputPropsasElInputProps}from'element-plus' typeInputProps=ExtractPublicPropTypesElInputProps & {} constprops =withDefaults(definePropsInputProps(),{})constattrs =useAttrs()constslots =useSlots()const$props =mergeProps(attrs, props) /script template component:is="h(ElInput, $props, slots)"//template「?? component 组件为什么可以传入 h 函数 ?」 h函数用于创建虚拟 DMO 节点(vnode),is 属性接收到一个函数时,也就是h(ElInput, $attrs, $slots),会立即执行并返回一个 VNode,这个 VNode 描述了如何渲染 ElInput 组件。 处理 ref「问题: 封装时怎么如何导出原组件实例方法?」 在二次封装子组件时,为了让父组件能够获取子组件的 ref, 并能够调用一些原有的方法,我们还需要将子组件的方法暴露出去。 对于这个需求,网上方法五花八门,但是在 Vue3 的 setup 模板中,我个人认为,「其实并没有特别优雅的方式」。 1. 向父组件暴露 ref 函数思路:创建一个getRef的函数,把ref暴露出去, 父组件调用getRef方法后在执行子组件方法的调用: scriptsetuplang="ts"import{ ref }from'vue' constinputRef =ref()constgetRef= () = inputRef.value defineExpose({ getRef })/script template el-inputref="rawRef"v-bind="{...$attrs, ...props}"//template2. 使用 Proxy 代理另一个思路,我们可以使用Proxy代理暴露出去的方法: scriptsetuplang="ts"import{ ref }from'vue'constrawRef =ref() defineExpose(newProxy({}, { get:(_target, key) =rawRef.value?.[key], // 因为代理的是一个空对象,用 has 判断一下,访问的属性是否存在 has:(_target, key) =keyin(rawRef.value|| {}) } ))/script templateel-inputref="rawRef"v-bind="{...$attrs, ...props}"//templatenew Proxy().has | MDN3. 使用 vm.exposedscriptsetuplang="ts"constprops =defineProps()constvm =getCurrentInstance() constchangeRef= (inputInstance) = { vm!.exposed= inputInstance || {}// 其实父组件不是直接拿到这个 exposed 的,拿的是子组件的代理对象,// 不能只改变 exposed 的值,还要改变 exposeProxy 的值 vm!.exposeProxy= inputInstance || {}// 上面代码也可以直接写成: vm!.exposeProxy = vm!.exposed = inputInstance || {}}/script template el-inputref="changeRef"v-bind="{...$attrs, ...props}"//template「Why ?」 我们添加一个 defineExpose 导出{ a: 1, b: 2 },然后打印vm.exposed和 changeRef 方法中返回的value: scriptsetuplang="ts"import{ getCurrentInstance }from'vue' constvm =getCurrentInstance()constprops =defineProps() console.log('vm===',vm.exposed) constchangeRef= (inputInstance) = {console.log('value===',inputInstance)vm.exposed= inputInstance ?? {}} defineExpose({a:1,b:2})/script templateElInput:ref="changeRef"v-bind="{...$attrs, ...props}" templatev-for="(_, name) of $slots"#[name]="scop" slot:/slot /template/ElInput/template我们看看控制台打印是什么 在子组件的 ref 传递一个函数changeRef,在这个函数中,可以拿到原先组件(el-input)的对外暴露的对象 (方法);getCurrentInstance获取的是当前组件的实例,vm.exposed拿到的是 defineExpose 导出的{ a: 1, b: 2 }也就是说!vm.exposed其实就是当前组件defineExpose({})对外抛出的对象,所以我们只要在changeRef函数中,设置vm.exposed = inputInstance,就可以再次把 el-input 对外暴露的方法暴露给父组件。 推荐阅读:巧妙使用 Vue.extend 继承组件实现 el-table 双击可编辑 | 知乎[2],回顾一下 Vue 2 中实例的高可玩性。 处理 Typescript 类型1. 完善 props 类型提示importtype{InputPropsasElInputProps}from'element-plus'constprops =withDefaults(definePropsElInputProps(), {})2. 完善 $slots 类型提示import{ElInput}from'element-plus'typeInputSlots=InstanceTypetypeofElInput['$slots']defineSlotsInputSlots()3. 完善 $expose 类型提示这里的$expose指的是组件实例对外暴露的方法,如:Input 组件的 emit 事件(@foucs、@blur)等。 importtype{InputInstanceasElInputInstance}from'element-plus'defineExposeElInputInstance()终极版本 (TS 类型完备)基础版使用 proxy 暴露方法 : script setup lang="ts"importtype{InputInstanceasElInputInstance,InputPropsasElInputProps}from'element-plus'import{ElInput}from'element-plus'import{ mergeProps }from'vue' typeInputProps=ExtractPublicPropTypesElInputProps /* 可以在此处添加新的 props */}typeInputSlots=InstanceTypetypeofElInput['$slots'] & {/* 可以在此处添加新的 slot */}typeInputInstance=ElInputInstance& {/* 可以在此处添加新的组件实例方法 */} constprops =withDefaults(definePropsInputProps(), {clearable:true,// 改变el-input clearable 默认值/* 可以在此处为新属性添加默认值 */}) constrawRef = refInputInstance() defineSlotsInputSlots()defineExposeInputInstance(newProxy( {}, { get:(_target, key) =rawRef.value?.[keyaskeyofInputInstance], has:(_target, key) =keyin(rawRef.value|| {}), }, )asInputInstance,)/script templateElInputv-bind="mergeProps($attrs, props)"ref="rawRef"class="e-input" templatev-for="(_, name) in $slots"#[name]="scope" slot:/ /template/ElInput/template stylelang="scss"scoped.e-input{min-width:190px; // 添加新样式 // :deep(xxx) {} 覆盖原有样式}/styleh 函数版script setup lang="ts"importtype{InputInstanceasElInputInstance,InputPropsasElInputProps}from'element-plus'importtype{ExtractPublicPropTypes}from'vue'import{ElInput}from'element-plus'import{ h }from'vue' typeInputProps=ExtractPublicPropTypesElInputProps & { }typeInputSlots=InstanceTypetypeofElInput['$slots'] & {}typeInputInstance=ElInputInstance& {} constprops =withDefaults(definePropsInputProps(), {}) constvm =getCurrentInstance()constchangeRef= (inputInstance: InputInstance) = vm!.exposeProxy= vm!.exposed= inputInstance || {} defineSlotsInputSlots()// TS插槽类型提示defineExposeInputInstance()// 实例类型提示(组件上的事件)/script templatecomponent:is="h(ElInput, { ...$attrs, ...props, ref: changeRef as any }, $slots)"//template使用示例: template div h3?? 父组件/h3 YiInputref="inputRef"v-model="msg"placeholder="请输入内容" template#append el-iconSearch//el-icon /template template#suffix el-iconUser//el-icon /template /YiInput /div/template scriptlang="ts"setupimport{Search,User}from'@element-plus/icons-vue'importtype {InputInstance}from'element-plus'constinputRef = refInputInstance()constmsg =ref('Hello world') setTimeout(() ={ inputRef.value?.focus()// 自动聚焦 inputRef.value?.clear()},3000)/scriptAI编程资讯AI Coding专区指南:https://aicoding.juejin.cn/aicoding 点击"阅读原文"了解详情~ 阅读原文

上一篇:2021-06-23_广告门公开课回归 ∶ 大V的新公司 , 会怎么做社交增长营销 下一篇:2020-01-07_德国发布AI和数据伦理的75项建议,提出数据和算法协同治理等理念 | 腾讯网络法专报

TAG标签:

16
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价