大家好,我是考100分的小小码 ,祝大家学习进步,加薪顺利呀。今天说一说vue弹出模态框dialog_android各种弹出层,希望您对编程的造诣更进一步.
日常项目开发中,经常需要封装弹出层组件,不同的人会有不同的封装方法,弹出层组件因为涉及到 v-model ,各种事件和显示隐藏的状态管理、数据的流动和父子组件通信,可以设计的很简单,也可以设计的很复杂,管理不善会给后期维护带来诸多麻烦,这里我总结了一些弹出层的封装方法,比较了优劣,方便以后在处理类似问题上可以很清晰的选择合适的方案。
问题
- 页面很多弹窗,代码已经上千行,细节太多,维护时容易出错
- 弹窗的状态和事件应该在哪一层管理?
- 如何封装弹出层组件?
「导致单文件组件代码过长的几个原因」
- 循环列表: 循环项的结构复杂,嵌套多
- 没有分组件: 各个独立区域没有封装都在一个页面
- Form表单: 表单项多(rules配置项)
- Table列表: 列数多,列数要格式化(列配置项)
- 弹出层: 弹出层内容结构复杂(多个弹出层未封装)
- 业务逻辑没有抽象和分层
- 工具类函数和静态数据配置
「如何避免单文件组件的代码长度」
- 单文件组件代码长度控制在200行左右
- 弹出层、表单、独立区域、列表都封装成组件 ./components
- 工具类、配置类单独抽离出来 ./utils ./config.js
- 相似的代码进行重构
- 减少嵌套层级,使用 async await
- 合理的使用Mixin 抽象公共逻辑
弹出层组件的花式用法
状态由父组件管理,只封装Modal 内容
<template>
<a-button type="primary" @click="visible1=true">
打开Modal1
</a-button>
<a-modal v-model="visible1" @ok="visible1=false">
<ModalContent />
</a-modal>
</template>
<script> export default { data() { return { visible1: false }; }, } </script>
这种将modal的状态和事件放在了父组件这一层,内容数据处理逻辑放到了组件 ModalContent 里面
这种用法的好处是方便控制Modal的状态,但缺点是Modal不能复用,只能复用内容,父组件中需要管理
modal 的状态和事件。
封装整个Modal,状态由Modal内部管理,watch监听 显示隐藏
<template>
<div>
<a-button type="primary" @click="visible2=true">
打开Modal2
</a-button>
<TestModal2 :show="visible2" @ok="visible2=false"/>
</div>
</template>
<script> export default { data() { return { visible2: false }; }, } </script>
// TestModal2
<template>
<a-modal v-model="visible" @click="$emit('ok')">
<p>{{ form.name || ''}}</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</a-modal>
</template>
<script> export default { props: { show: false, }, data(){ return { visible: false, form: { name: '张三' } } }, watch: { show: { immediate: true, handler(v){ this.visible = v; } } } } </script>
子组件需要定义props、需要watch 父组件传递进来的 show 去更新visible
❝
这里不直接使用props 做为modal的v-modal的值,是因为这样会导致数据传递混乱,父子组件都在同时管理show状态,同时也会产生bug,modal关闭后,父组件无法再次打开modal
❞
Modal状态透传
<template>
<div>
<a-button type="primary" @click="visible3=true">
打开Modal3
</a-button>
<TestModal3 :visible="visible3" @ok="visible3=false"/>
</div>
</template>
<script> export default { data() { return { visible3: false }; }, } </script>
// TestModal3
<template>
<a-modal v-bind="$attrs" v-on="$listeners">
<p>{{ form.name || ''}}</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</a-modal>
</template>
<script> export default { data(){ return { form: { name: '张三' } } }, } </script>
利用子组件根节点绑定 和listeners 可以属性和事件通过父组件透传到子组件,在父组件使用封装的Modal组件时,相当于直接使用 Modal 组件
但缺点也很明显,所有的属性和事件都被父组件接收了,如果要使用modal 组件定义之外的属性和事件,需要从 和listeners 获取,取值比较隐蔽,同时父组件要管理 modal的状态和事件
【推荐用法】使用Ref调用
<template>
<div>
<a-button type="primary" @click="showModal4">
打开Modal4
</a-button>
<TestModal4 ref="testModal4" @ok="onOk4"/>
</div>
</template>
<script> export default { methods: { showModal4() { this.$refs.testModal4.show({name: '李四'}); }, onOk4(data){ console.log(data); this.$refs.testModal4.close(); } }, } </script>
// TestModal4
<template>
<a-modal v-model="visible">
<p>{{ form.name || ''}}</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<a-button type="primary" @click="onOk">确认</a-button>
</a-modal>
</template>
<script> export default { data(){ return { visible: false, form: { name: '张三' } } }, methods: { show(initData){ this.visible = true; if(initData) { this.form = {...initData } }else{ this.form = { name: '张三' } } }, close(){ this.visible = false; }, onOk(){ this.$emit('ok', this.form); } } } </script>
这里父组件利用refs 来调用子组件的方法,Modal 所有状态和逻辑都由Modal内部管理,父组件使用Modal组件时,只需要通过 refs 来调用即可,不需要通过prop 传递,使用更加灵活,通过 调用 refs.modal.show() 可以传递相关数据给子组件进行初始化,子组件复用性也比较好,可以在任意其它业务中复用。
使用JS函数式直接调用
在基于 refs 封装的基础之上,可再封装成函数式方法调用,不用再引用组件,直接使用函数调用,类型 组件库中常见的Message.show({}), this.$dialog.show() 直接显示一个弹出层组件
import Vue from 'vue';
import EditUserModal from './index.vue';
const createInstance = (options = {}) => {
const div = document.createElement('div');
const el = document.createElement('div');
div.appendChild(el);
document.body.appendChild(div);
const modalProps = { props: {}, ref: 'editUserModal' };
let instance = null;
const refName = modalProps.ref;
const { data = {}, isEdit = false, onOk, onCancel, onDelete, onAfterclose, ...otherProps } = options;
const disdroy = () => {
if (!instance) return;
instance.$destroy();
instance = null;
div.parentNode.removeChild(div);
};
const render = (props) => {
const handleAfterClose = () => {
onAfterclose && onAfterclose();
disdroy();
};
const modalProps = {
props: otherProps,
on: {
cancel: () => {
onCancel && onCancel();
},
ok: (data) => {
onOk && onOk(data);
},
delete: () => {
onDelete && onDelete();
},
},
};
return new Vue({
el,
render() {
return <EditUserModal {...modalProps} ref={refName} onAfterclose={handleAfterClose} />;
},
});
};
instance = render(otherProps);
instance.$refs[refName].show(data, isEdit);
return instance;
};
/** * 函数式调用 编辑用户弹出层 * @param {*} options.onCancel * @param {*} options.onOk * @param {*} options.onDelete * @param {*} options.onAfterclose * @param {*} options.data * @param {*} options.isEdit * @example editUserModal.show({ onOk: (data) => {}, title: ’编辑用户'}) */
const show = (options) => createInstance(options);
export const editUserModal = { show };
这里的封装逻辑基本也是通用的,在组件库开发中是非常有用的,可以简单开发者的调用方式,在平常的业务开发中,封装这样的方式有一定的成本,如果是跨业务十分通用的弹出层组件可以这样封装。
这种封装要点是要处理好弹出层关闭后的逻辑,必须销毁掉实例,并从真实节点中移除,另外也是处理好show方法的传参问题,是否可以覆盖到组件的api。
弹出层通用逻辑封装为Mixin
利用Vue2 的 mixin 可以实现类似继承的特性,很方便复用一些逻辑
/** * modal.mixin.js * 通用弹出层逻辑封装 * 用于编辑或新增弹出层,也可用于普通展示弹出层 */
import { cloneDeep } from 'lodash-es';
export default {
props: {
width: {
type: Number,
default: 520,
},
title: {
type: String,
default: '编辑',
},
},
data() {
return {
// 是否为编辑
isEdit: false,
// 隐藏/显示
visible: false,
// 用于重置表单
defaultForm: {},
// 用于存储表单数据,双向绑定
form: {},
};
},
methods: {
// ------------------------- 对外接口(可覆盖,可通过ref调用)
// 显示
show(data, isEdit = false) {
this.isEdit = isEdit;
this.visible = true;
this.form = this.formatEditData(cloneDeep({ ...this.defaultForm, ...data }));
},
// 关闭
close() {
this.visible = false;
},
// 处理提交时的数据 [外部重写]
formatSubmitData(data) {
return data;
},
// ------------------------- 私有方法
// 关闭之后 重置表单
_handleAfterClose() {
this.$refs.modalForm && this.$refs.modalForm.resetFields();
this.$emit('afterclose');
},
// 取消/关闭
_handleCancel() {
this.$emit('cancel');
this.close();
},
_handleDelete() {
this.$emit('delete');
this.close();
},
// 确认
_handleOk() {
// 兼容非表单弹出层
if (!this.$refs.modalForm) {
this.$emit('ok');
this.close();
return;
}
// 表单不自动关闭,需要手动关闭
this.$refs.modalForm.validate((v) => {
if (!v) return;
// 提交之前做些处理
const formData = cloneDeep(this.form);
const data = this.formatSubmitData(formData);
this.$emit('ok', data);
});
},
},
};
这个 mixin 可以用于普通的弹窗和表单弹窗,内置了显示隐藏的处理逻辑,在实现层只需要关心业务逻辑,也可以覆盖相关方法来扩展相关Modal的逻辑。
小结
弹出层组件的合理封装有利于代码的后期维护,也方便在业务中复用相关逻辑,不同的场景有不同的封装方式,这里我推荐在业务开发中尽量使用 refs 的封装方式,逻辑更聚焦,与父组件耦合度低,复用性强,也能进一步抽象出mixin,进一步复用,另外在组件库开发中,尽量提供函数式调用的方式,可以简化使用者的调用方式。
Demo源码 Github(vite+vue2.x)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/13843.html