场景说明
在父组件中通过给子组件传递不同参数,在同一popup组件中展示不同数据。
问题描述
编译后在chrome浏览器中没问题,但在 android 真机环境会停在 this.$nextTick 的位置(通过console.log 确定),不往下执行代码,控制台也无报错信息。
父组件代码:
<template>
<view>
<view @click="showList1"></view>
<view @click="showList2"></view>
<common-popup ref="showList" :type="popupType" @initCallback="popupCheck" />
</view>
</template>
<script>
import commonPopup from '@/components/common-popup.vue';
export default {
components: {
commonPopup
},
data() {
popupType: ''
},
methods: {
//原始写法
//showList1() {
// this.popupType = "getList1";
// this.$nextTick(() => { //停在该处,init和后续代码也未执行
// this.$refs.showList.init();
// });
// 其它逻辑代码......
//},
//showList2() {
// this.popupType = "getList2";
// this.$nextTick(() => { //停在该处,init和后续代码也未执行
// this.$refs.showList.init();
// });
// 其它逻辑代码......
//},
//改正后写法
async showList1() {
this.popupType = "getList1";
await new Promise(resolve => {
setTimeout(() => this.$nextTick(resolve), 0);
});
// 其它逻辑代码......
},
async showList2() {
this.popupType = "getList2";
await new Promise(resolve => {
setTimeout(() => this.$nextTick(resolve), 0);
});
// 其它逻辑代码......
},
popupCheck(res) {
if (res) {
this.$refs.showList.open();
}
}
}
}
</script>
子组件代码:
// common-popup.vue
<template>
<view>
<uni-popup ref="popup" type="bottom" :background-color="'#fff'"></uni-popup>
</view>
</template>
<script>
export default {
name: 'common-popup',
props: {
type: {
type: [String, Number],
default: "",
}
},
data() {},
methods: {
init() {
uni.showLoading({
title: '初始化中'
});
switch (this.type) {
case 'getList2':
this.getList2();
break;
default:
this.getList1();
break;
}
},
getList1() {
uni.request({
url: '/getList1',
data: {},
success: (res) => {
uni.hideLoading();
uni.hideToast();
if (res == 1) {
this.$emit('initCallback', true)
} else {
this.$emit('initCallback', false)
}
}
})
},
getList2() {
uni.request({
url: '/getList2',
data: {},
success: (res) => {
uni.hideLoading();
uni.hideToast();
if (res == 1) {
this.$emit('initCallback', true)
} else {
this.$emit('initCallback', false)
}
}
})
},
open() {
this.$refs.popup.open()
}
}
}
</script>
原因分析
在 Android 真机上 $nextTick 未触发的问题,通常与 平台对微任务/宏任务的支持差异 相关。
根本原因
微任务(Microtask)队列未被触发
浏览器环境:Vue 的 $nextTick 默认使用微任务(如Promise、MutationObserver),能立即执行。
Android WebView:部分旧版本 WebView 对微任务支持不完善,导致 $nextTick 回调未被触发,代码卡在 await。
解决方案(避免依赖 setTimeout)
1. 强制使用宏任务触发 $nextTick
// 父组件
async showList1() {
try {
this.popupType = "getList1";
// 修复:用宏任务包裹 $nextTick
await new Promise(resolve => {
setTimeout(() => this.$nextTick(resolve), 0);
});
await this.$refs.showList.init();
} catch (err) {
console.error('错误:', err);
}
}
2. 兼容 Vue 2 的 $nextTick 写法
// 兼容所有环境的写法
function safeNextTick(vm) {
return new Promise(resolve => {
if (typeof Promise !== 'undefined') {
vm.$nextTick(resolve);
} else {
setTimeout(() => vm.$nextTick(resolve), 0);
}
});
}
// 调用
await safeNextTick(this);
问题总结与核心原理
为什么 强制使用宏任务
能解决问题?
平台差异性:
浏览器环境:Vue 的 this.$nextTick() 默认使用微任务(如 Promise、MutationObserver),能立即触发回调。
Android WebView:部分旧版本对微任务队列的支持存在缺陷,导致 this.$nextTick() 的回调未被及时执行,代码卡在 await。
宏任务的可靠性:
setTimeout 属于宏任务(Macrotask),其执行时机在事件循环的下一轮。
在兼容性较差的环境(如旧 Android WebView)中,宏任务队列的触发更稳定,从而绕过微任务的缺陷。
扩展建议
子组件增强健壮性(可选):
若子组件的 init 需要处理复杂异步逻辑,可保留 Promise 封装以支持更精细的控制:
// 子组件(可选优化)
methods: {
init() {
return new Promise((resolve, reject) => {
switch (this.type) {
case 'getList2':
this.getList2().then(resolve)
.catch(reject);
break;
default:
this.getList1().then(resolve)
.catch(reject);
break;
}
});
}
}
全局兼容性处理:
在项目入口文件中封装通用的 $safeNextTick 方法,避免重复代码:
// main.js(Vue 2)
Vue.prototype.$safeNextTick = function() {
return new Promise(resolve => {
setTimeout(() => this.$nextTick(resolve), 0);
});
};
调用时:
await this.$safeNextTick();