小程序热编译之后,导致自定义弹窗无法关闭
前言
最近遇到一个需求,需要在小程序各个页面通过传入页面路径,来获取当前页面的营销任务,根据任务配置弹出弹框。
解决方案是通过自定义弹窗,统一劫持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
})
}
}
})
结尾
该方案只是避免了弹窗点击无反应的问题,如果有更好的解决方案,欢迎下方留言,一起学习~~
转载自:https://juejin.cn/post/7254083731487588389