uni-app(vue2)组件动态传参问题

发布于:2025-04-02 ⋅ 阅读:(35) ⋅ 点赞:(0)

场景说明

在父组件中通过给子组件传递不同参数,在同一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 默认使用微任务(如PromiseMutationObserver),能立即执行。

  • 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);

问题总结与核心原理

为什么 强制使用宏任务 能解决问题?
  1. 平台差异性

    • 浏览器环境:Vue 的 this.$nextTick() 默认使用微任务(如 PromiseMutationObserver),能立即触发回调。

    • Android WebView:部分旧版本对微任务队列的支持存在缺陷,导致 this.$nextTick() 的回调未被及时执行,代码卡在 await

  2. 宏任务的可靠性

    • 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();