vant2中actionSheet的组件封装,使其支持函数调用

基础版

        用过vant的家人们肯定都知道其中的Dialog组件是支持函数调用和组件调用的,组件调用就不阐述了,其中函数调用的使用方式如下:

Dialog.alert({ 
    title: '标题', 
    message: '弹窗内容', 
}) 
.then(() => { 
    // on close 
});

产生的效果图:

        感觉Dialog的函数调用特别方便啊,而vant中有个跟Dialog很像(自认为)的组件,那就是ActionSheet动作面板这个组件。

        为什么说像呢,因为他们其实都是触发了点击事件以后出来一个弹框,然后弹框上会有对应的事件,对此,提出:为什么ActionSheet这个组件为什么没有函数调用的方法呢?(其实是我导师提的,我发散性思维不好)于是就有了这篇文章!

        先捋捋思路,想要封装成函数调用,也就是在业务代码中我们可以直接使用函数就可以渲染相关的页面,那怎么做到不在template中写对应的组件但却可以进行渲染呢?说明是需要使用到动态渲染操作的,也就是在触发某一事件的时候才会渲染相关的组件,那么我们得先把对应的组件写好先。


新建一个showActionSheet文件夹,再新建一个actionSheet.vue文件:

<template>
    <van-action-sheet
        v-model="visible"
        :actions="actionsList"
        @select="selectAction"
        @closed="close"
        get-container="body"
    ></van-action-sheet>
</template>

<script>
export default {
    name: "actionSheet",
    props: {
        visible: {
            type: Boolean,
            default: false
        },
        actionsList: {
            type: Array,
            default: () => []
        },
        selectAction: Function
    },
    methods: {
        close() {
            console.log("close");
            this.visible = false;
            this.$emit("close");
        }
    }
};
</script>

然后再在该文件夹下新增一个showActionSheet.js文件,在这个文件里定义函数并抛出:

import Vue from "vue";
import actionSheet from "./actionSheet.vue";

// 定义showActionSheet函数,接收对象参数
function showActionSheet({actionsList, selectAction }) {
    // 使用Vue.extend方法将actionSheet组件扩展为一个新的构造器,该构造器可以用来创建新的实例
    const actionSheetConstructor = Vue.extend(actionSheet);
    // 设置visible控制组件是否可见,初始值为true
    let visible = true;
    
    // 创建新的vue实例,并将对应的参数传递给这个实例
    const instance = new actionSheetConstructor({
        propsData: { visible, actionsList, selectAction }
    });
    // 挂在组件:使用$mount方法将该实例挂载到一个虚拟DOM节点上,若未指定挂载点,则会默认挂载到内存的虚拟DOM节点
    instance.$mount();
    // 将组件的根元素添加到页面的body中,使其在页面上可见
    document.body.appendChild(instance.$el);

    // 监听close事件
    instance.$on("close", () => {
        // 若close事件触发,则执行移除组件根DOM元素操作
        document.body.removeChild(instance.$el);
        // 销毁vue实例,释放资源
        instance.$destroy();
    });
    // 返回vue实例,使得调用者可通过这一步获取这个vue实例
    return instance;
}

export default showActionSheet;

在想要调用的代码中直接可以通过如下代码进行函数调用:

methods: {
    test() {
        const actionsList = [{ name: "选项一" }, { name: "选项二" }, { name: "选项三" }]
        showActionSheet({
            actionsList: actionsList,
            selectAction: (selectObj) => {
                console.log("我选择了", selectObj)
            }
        })
    },
}

由此,成功实现,效果图:

进阶版(Promise调用)

        第一版只是单纯通过传入对应的参数设置相关事件,但实际使用中,为了更符合开发规范,更贴合开发思路,决定结合Promise进行进一步的改造。
        actionSheet.vue文件主要的修改点在于将对应的事件和参数都进行了接入,若用户在调用时传入对应参数,即刻修改组件上的对应方法。

<template>
	  <van-action-sheet
		v-model="visible"
		:title="title"
		:description="description"
		:actions="actionsList"
		:cancel-text="cancelText"
		:round="round"
		:lock-scroll="lockScroll"
		:lazy-render="lazyRender"
		:close-on-popstate="closeOnPopstate"
		:close-on-click-action="closeOnClickAction"
		:close-on-click-overlay="closeOnClickOverlay"
		:safe-area-inset-bottom="safeAreaInsetBottom"
		get-container="body"
		@select="onSelect"
		@cancel="cancel"
		@open="open"
		@opened="opened"
		@close="close"
		@closed="onClosed"
		@click-overlay="clickOverlay"
	  ></van-action-sheet>
  </template>

<script>
export default {
	name: "actionSheet",
	props: {
		visible: {
			type: Boolean,
			default: false
		},
		title: {
			type: String,
			default: ""
		},
		description: {
			type: String,
			default: ""
		},
		actionsList: {
			type: Array,
			default: () => []
		},
		cancelText: {
			type: String,
			default: "取消"
		},
		duration: {
			type: Number,
			default: 0.3
		},
		round: {
			type: Boolean,
			default: true
		},
		overlay: {
			type: Boolean,
			default: true
		},
		lockScroll: {
			type: Boolean,
			default: true
		},
		lazyRender: {
			type: Boolean,
			default: true
		},
		closeOnPopstate: {
			type: Boolean,
			default: false
		},
		closeOnClickAction: {
			type: Boolean,
			default: true
		},
		closeOnClickOverlay: {
			type: Boolean,
			default: true
		},
		safeAreaInsetBottom: {
			type: Boolean,
			default: true
		},
		cancel: {
			type: Function,
			default: () => ({})
		},
		open: {
			type: Function,
			default: () => ({})
		},
		opened: {
			type: Function,
			default: () => ({})
		},
		close: {
			type: Function,
			default: () => ({})
		},
		closed: {
			type: Function,
			default: () => ({})
		},
		clickOverlay: {
			type: Function,
			default: () => ({})
		},
		select: {
			type: Function,
			default: () => ({})
		}
	},
	methods: {
		onSelect(selectObj) {
			this.select(selectObj)
			this.$emit("select", selectObj);
		},
		onClosed() {
			this.closed()
			this.$emit("closed");
		},
	}
};
</script>

showActionSheet.js的修改如下:

import Vue from "vue";
import actionSheet from "./actionSheet.vue";

// 全局定义一个对象
let instance;
// 全局创建一个新的构造函数
const actionSheetConstructor = Vue.extend(actionSheet);
// 定义初始化函数
const initInstance = () => {
    // 创建一个新的 Vue 组件实例,并指定初始状态
    instance = new actionSheetConstructor({
        el: document.createElement("div"),
        propsData: {
            visible: false,
            actionsList: [],
        }
    });
    // 将组件添加到页面上
    document.body.appendChild(instance.$el);
    // 在注册对应实例后,监听浏览器返回事件,卸载组件
    window.addEventListener("popstate", function(event) {
        document.body.removeChild(instance.$el);
        instance = null;
    });
}
// 定义触发函数
const ActionSheet = (options) => {
    if (!instance) {
        initInstance();
    }

    Object.assign(instance, {
        visible: true,
        ...options
    });

    return new Promise((resolve, reject) => {
        let selectWatcher
        let lastSelectObj;
        const handleSelect = (selectObj) => {
            lastSelectObj = selectObj
            // 判断是否有传入closeOnClickAction这个选项,若未传入或传入为true,说明选择后立刻关闭弹窗,即马上调用resolve
            if (options.closeOnClickAction == undefined || options.closeOnClickAction == true) {
                resolve(selectObj)
                instance.visible = false;
            } else if (!selectWatcher) {
                // 若为false,那就添加对visible的监听,一旦关闭,马上resolve
                selectWatcher = instance.$watch("visible", (val) => {
                    if (!val) {
                        resolve(lastSelectObj);
                        selectWatcher();
                    }
                });
            }
        };
        // 使用on,防止用户若有选择后弹窗不消失,修改选项的情况
        instance.$on("select", handleSelect);
        使用once的原因是因为关闭只会存在一次操作
        instance.$once("closed", () => {
            instance.visible = false;
            // 及时卸载对应select函数
            instance.$off("select", handleSelect);
            reject(new Error("closed"));
        });
    });
};

export default ActionSheet;

调用:

test() {
    const actionsList = [{ code: "1", name: "选项一" }, { code: "2", name: "选项二" }, { code: "3", name: "选项三" }]
    showActionSheet({
        actionsList: actionsList
    }).then((item) => {
        console.log("User selected:", item.name);
    }).catch((error) => {
        console.log(error.message);
    });
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值