likes
comments
collection
share

小程序热编译之后,导致自定义弹窗无法关闭

作者站长头像
站长
· 阅读数 22

前言

最近遇到一个需求,需要在小程序各个页面通过传入页面路径,来获取当前页面的营销任务,根据任务配置弹出弹框。

解决方案是通过自定义弹窗,统一劫持Page的onLoad方法,最后经js调用,打开弹窗。

小程序热编译之后,导致自定义弹窗无法关闭

遇到的问题

开发过程中遇到一个问题,每次代码保存触发小程序热编译之后,自定义弹窗便无法关闭。

问题排查

因为本地多次触发弹窗,都会正常打开和关闭,但热编译执行之后,弹窗再次打开,便无法关闭,测试之后发现是tap点击事件不响应了。

经过debugger排查,发现热编译之后,营销任务组件会重新编译,而context.selectComponent获取到的组件实例依然是旧的实例,此时旧的实例其实已经被销毁,但页面中依然存在,所以导致此时页面弹窗中的tap事件点击无响应。

// 获取上下文
const context = getContext()
// context中的营销任务组件实例
const dialog = context.selectComponent(newOptions.selector)
// 营销任务组件attached执行之后保存的最新实例
console.log(currentDialog.instance === diaog) // false

解决思路

热编译时,组件会被重新编译,此时会依次触发旧组件实例的detached方法和新组件实例的attached方法,可以从中获取到最新的营销组件实例。

此时即使拿到最新的实例,但是页面中表现得实例还是已销毁的,最新实例的数据变更并不会体现到页面上。所以目前能做的只是在新旧实例不一致时,如果当前旧的实例弹窗处于打开状态,则自动将其关闭,避免产生错误。

(暂时未发现有什么刷新组件实例的api,目前只能是先关闭,避免影响页面)

解决方案

dialog.js中导出了一个对象,用于保存当前营销组件实际的组件实例

营销组件初始化时,保存当前组件实例到currentDialog.instance中,销毁时置为null

// marketing-dialog.js
import { currentDialog } from './dialog'

Component({
    lifetimes: {
        attached() {
            currentDialog.instance = this
        },
        detached() {
            currentDialog.instance = null
        }
    }
})

dialog.js执行时,永远使用最新的实例进行数据变更,如果页面中旧的实例弹窗处于开启状态,则直接关闭。

// dialog.js
const Dialog = function (options = {}) {
    const newOptions = { ...defaultOptions, ...options }
    const context = getContext()
    // 热编译之后为旧的
    const dialog = context.selectComponent(newOptions.selector)
    if (currentDialog.instance) {
        delete newOptions.selector
        wx.nextTick(() => {
            // 关键代码
            currentDialog.instance.setData(newOptions)
            // 热编译之后为false
            console.log(currentDialog.instance === diaog) // false
            // 避免旧的弹窗依然开着
            if (dialog !== currentDialog.instance && dialog.data.visible) {
                dialog.setData({
                    visible: false
                })
            }
        })
    } else {
        throw new Error('未找到#marketingDialog节点,请确认selector是否正确')
    }
}

// 保存最新实例
export const currentDialog = {
    instance: null
}

部分源码

dialog.js
// dialog.js
const defaultOptions = {
    visible: false,
    selector: '#marketingDialog'
}

function getContext () {
    const pages = getCurrentPages()
    return pages[pages.length - 1]
}

const Dialog = function (options = {}) {
    const newOptions = { ...defaultOptions, ...options }
    const context = getContext()
    const dialog = context.selectComponent(newOptions.selector)
    if (currentDialog.instance) {
        delete newOptions.selector
        wx.nextTick(() => {
            currentDialog.instance.setData(newOptions)
            if (dialog !== currentDialog.instance && dialog.data.visible) {
                dialog.setData({
                    visible: false
                })
            }
        })
    } else {
        throw new Error('未找到#marketingDialog节点,请确认selector是否正确')
    }
}

Dialog.open = (options = {}) => Dialog({
    visible: true,
    ...options
})

export const currentDialog = {
    instance: null
}

export default Dialog
marketing-dialog.js
// marketing-dialog.js
import { currentDialog } from './dialog'

Component({
    /**
     * 组件的属性列表
     */
    properties: {
        visible: {
            type: Boolean,
            value: false
        },
        url: {
            type: String,
            default: ''
        },
        onConfirm: {
            type: Function
        },
        onClose: {
            type: Function
        }
    },
    lifetimes: {
        attached() {
            currentDialog.instance = this
        },
        detached() {
            currentDialog.instance = null
        }
    },
    /**
     * 组件的方法列表
     */
    methods: {
        handleConfirm () {
            const { onConfirm } = this.data
            this.changeVisible(false)
            onConfirm && onConfirm()
        },
        handleClose () {
            const { onClose } = this.data
            this.changeVisible(false)
            onClose && onClose()
        },
        changeVisible (bool) {
            this.setData({
                visible: bool
            })
        }
    }
})

结尾

该方案只是避免了弹窗点击无反应的问题,如果有更好的解决方案,欢迎下方留言,一起学习~~