- A+
?Hi~ 大家好,我是小鑫同学,一位长期从事前端开发的编程爱好者,我将使用更为实用的案例输出更多的编程知识,同时我信奉分享是成长的唯一捷径,在这里也希望我的每一篇文章都能成为你技术落地的参考~
?技术&代码分享
?推荐几个好用的工具
- var-conv 适用于VSCode IDE的代码变量名称快速转换工具
- generator-vite-plugin 快速生成Vite插件模板项目
- generator-babel-plugin 快速生成Babel插件模板项目
进入正题
在各种场景的开发中Dialog组件的出现频率都是非常高的,Dialog组件作为一个容器组件受容器内业务代码复杂度的影响,代码行数、变量及函数的定义可能会很多,这样的组件就一定要考虑封装使用,以保证主流程代码的简洁。下面一起来看一下如何利用面向对象的思想来封装它吧~
技术选型
Vuejs3.x
+ Typescript
+ <script setup>
是目前编写SFC组件的推荐方式,相比直接使用setup
函数编码体验会更好,AntdV
组件库的使用频率相比ElementUI
要高,推荐新项目直接选用AntdV
,完整代码可以在1024Code直接Fork或导出到本地。
代码分析
下面的代码来自AntdV
组件库Model
组件的示例,Model
需要一个visible
属性来控制是否显示和隐藏,由button
的click
事件来触发修改visible
属性显示,并且可以通过ok
事件的触发来表示对Model内业务逻辑的认可。
从这个示例来看代码当然是没有拆分组件的必要的,当但代表主流程业务的button
和由modal
包裹的业务流程变得多起来的时候,script
里面的代码可读性将大大降低。
<script setup lang="ts"> import { ref } from 'vue'; const visible = ref<boolean>(false); const showModal = () => { visible.value = true; }; const handleOk = (e: MouseEvent) => { console.log(e); visible.value = false; }; </script> <template> <a-button type="primary" @click="showModal">Open Modal</a-button> <a-modal v-model:visible="visible" title="Basic Modal" @ok="handleOk"> <p>Some contents...</p> <p>Some contents...</p> <p>Some contents...</p> </a-modal> </template> <style scoped> </style>
普通的组件拆分
常规的组件拆分大家按习惯来做一定是考虑通过Props
传递visible
参数来控制子组件中Model组件的显示,但是你是没有办法直接将Props
的visible
属性绑定到Model,因为在关闭弹窗是涉及到了修改变量,这样的做法在Vuejs
中认为是危险的(父子组件单向数据流),所以必须要通过子组件重新定义变量来接收后使用;
另一个问题就是父组件在给子组件传visible
参数时在父组件也有属性定义,这个属性在改为true
后同样因单向数据流的限制是无法在Model关闭时动态变更为false
,这样的Model就只能被打开一次,合理的解决范式就是通过emit
在Model关闭时通知(回调)父组件,由父组件主动修改它定义的变量。
这样拆分组件不仅定义了多份控制显示/隐藏的变量,并且还需要在回调中专门处理,这给组件的开发和使用都造成了一定的麻烦,所以我不推荐这种拆分方式。
面向对象拆分组件
面向对象的第一大概念就是封装,所以只要涉及封装就完全可以用面向对象的思想来做,在案例中控制显示隐藏的visible
属性和ok
事件都是属于这个组件对象自己的,只能定义在子组件中。操作Model显示隐藏的方式不能通过Props
传递,同样需要由组件对象自己提供,在使用了<script setup>
后,组件默认为关闭状态,需要使用defineExpose
函数将需要暴露的函数暴露给父组件使用。前面提到的emit
在这里还是比不可少了,但是仅仅是作为将业务处理的结果传递给父组件。
<script setup lang="ts"> import { ref, defineExpose, defineEmits } from 'vue' const visible = ref<boolean>(false); const open = () => visible.value = true; const close = () => visible.value = false; defineExpose({ open, }) const emit = defineEmits<{ (e: 'complete', code: number): void }>() const handleOk = (_) => { close(); emit('complete', 200); }; </script> <template> <a-modal v-model:visible="visible" title="Basic Modal" @ok="handleOk"> <p>Some contents...</p> <p>Some contents...</p> <p>Some contents...</p> </a-modal> </template> <style scoped> </style>
面向对象使用组件
在面向对象封装时并不需要过多的考虑显示隐藏状态变化的传递,属于组件本身的属性、函数有组件自己控制包括是否对外暴露等,那么在使用的时候也是非常简洁的。在父组件中只关注Model什么时候需要打开和关闭后回调的数据处理即可。
<script setup lang="ts"> import { ref } from 'vue'; import BusinessDialog from './components/BusinessDialog.vue'; const businessDialogRef = ref<InstanceType<typeof BusinessDialog>>(); const showModal = () => { businessDialogRef.value?.open(); } const onComplete = (e: any) => { console.log(e); } </script> <template> <a-button type="primary" @click="showModal">Open Modal</a-button> <BusinessDialog ref="businessDialogRef" @complete="onComplete" /> </template> <style scoped> </style>
总结
文中介绍的关于DialogComponent组件的封装思想是我所推荐的,因为它不仅开发子组件是不需要过多外部的干扰,在使用子组件是也不需要过多的考虑,这也是封装的意义所在,在其中使用到了defineExpose()
、defineEmits()
,对于很少使用<script setup>
方式的小伙伴也是不熟悉的,可以通过Vuejs文档学习,最后在父组件定义子组件的ref
时使用的InstanceType
属于Typescript
中的类型编程,感兴趣的也可以了解一下。好了,今天就说这么多,下次见~
PS:完整代码见1024Code;
如果看完觉得有收获,欢迎点赞、评论、分享支持一下。你的支持和肯定,是我坚持写作的动力~