欢迎光临
我们一直在努力

uniapp使用picker-view地址弹框,解决首次加载没有数据展示的问题

核心原因:

父组件请求地址接口是异步操作,但子组件(地址选择弹框)初始化时父组件还没拿到接口数据,导致首次传值为空 —— 简单说就是「数据传递时机跟不上组件渲染时机」

问题根源拆解

用一个时间线就能看明白:

  • 父组件渲染 → 子组件初始化 → 父组件给子组件传值(此时接口还在请求,值为空)
  • 接口请求完成 → 父组件的地址数据更新 → 但子组件已经初始化过,首次接收的是空值
  • 即使后续父组件数据更新,若子组件没监听数据变化,也不会同步更新
  • 解决方案(分 2 步,从根上解决)

    第一步:父组件优化(确保数据加载完成后再传值 / 渲染子组件)最简单最便捷的

    父组件要做两件事:① 标记数据加载状态 ② 要么等数据加载完再渲染子组件,要么主动通知子组件更新。

    vue

    <!– 父组件 –>
    <template>
    <view>
    <button @click="showAddressPicker = true">选择地址</button>

    <!– 方案1(推荐):数据加载完成前不渲染子组件,避免传空值 –>
    <address-picker
    v-if="addressDataLoaded" <!– 关键:数据加载完成后再渲染子组件 –>
    v-model="showAddressPicker"
    :address-list="addressList"
    />

    <!– 方案2:始终渲染子组件,但通过加载状态告知子组件 –>
    <!– <address-picker
    v-model="showAddressPicker"
    :address-list="addressList"
    :loading="!addressDataLoaded"
    /> –>
    </view>
    </template>

    <script setup>
    import { ref, onMounted } from 'vue';
    import AddressPicker from '@/components/AddressPicker.vue'; // 引入子组件

    // 响应式数据
    const showAddressPicker = ref(false);
    const addressList = ref([]); // 地址接口数据
    const addressDataLoaded = ref(false); // 数据加载完成标记

    // 请求地址接口
    const getAddressData = async () => {
    try {
    const res = await uni.request({
    url: '你的地址接口',
    method: 'GET'
    });
    if (res.statusCode === 200) {
    addressList.value = res.data.data; // 接口数据赋值
    addressDataLoaded.value = true; // 标记加载完成
    }
    } catch (error) {
    console.error('地址接口请求失败:', error);
    addressDataLoaded.value = true; // 即使失败也标记,避免组件一直不渲染
    }
    };

    // 页面初始化时请求数据
    onMounted(() => {
    getAddressData();
    });
    </script>

    第二步:子组件优化(监听父组件传值变化,同步更新)

    子组件必须监听父组件传递的 address-list,确保数据更新时能实时响应,而不是只在初始化时拿一次值。

    vue

    <!– 子组件 AddressPicker.vue –>
    <template>
    <uni-popup v-model="showPicker" type="bottom">
    <view class="picker-wrapper">
    <!– 加载中提示(可选) –>
    <view v-if="loading" class="loading-tip">加载中…</view>

    <!– 地址选择器:数据存在时才渲染 –>
    <picker-view
    v-else
    :value="pickerValue"
    @change="onPickerChange"
    class="picker-view"
    >
    <!– 省 –>
    <picker-view-column>
    <view v-for="(item, idx) in addressList" :key="idx" class="picker-item">
    {{ item.name }}
    </view>
    </picker-view-column>
    <!– 市 –>
    <picker-view-column>
    <view v-for="(item, idx) in currentCityList" :key="idx" class="picker-item">
    {{ item.name }}
    </view>
    </picker-view-column>
    <!– 区 –>
    <picker-view-column>
    <view v-for="(item, idx) in currentAreaList" :key="idx" class="picker-item">
    {{ item.name }}
    </view>
    </picker-view-column>
    </picker-view>
    </view>
    </uni-popup>
    </template>

    <script setup>
    import { ref, watch, defineProps, defineEmits, computed } from 'vue';

    // 接收父组件传值
    const props = defineProps({
    modelValue: { // 控制弹框显示隐藏(v-model 绑定)
    type: Boolean,
    default: false
    },
    addressList: { // 地址数据
    type: Array,
    default: () => []
    },
    loading: { // 加载状态(可选)
    type: Boolean,
    default: false
    }
    });

    // 定义事件
    const emit = defineEmits(['update:modelValue', 'confirm-address']);

    // 响应式数据
    const showPicker = computed({
    get: () => props.modelValue,
    set: (val) => emit('update:modelValue', val)
    });
    const pickerValue = ref([0, 0, 0]);
    const currentCityList = ref([]);
    const currentAreaList = ref([]);

    // 初始化省市/区县列表
    const initAreaList = () => {
    if (props.addressList.length === 0) return;
    // 默认选中第一个省份的城市
    currentCityList.value = props.addressList[pickerValue.value[0]].children || [];
    // 默认选中第一个城市的区县
    currentAreaList.value = currentCityList.value[pickerValue.value[1]]?.children || [];
    };

    // 监听父组件传的 addressList 变化(核心!)
    watch(
    () => props.addressList,
    (newList) => {
    if (newList.length > 0) {
    initAreaList(); // 数据更新后重新初始化列表
    }
    },
    { immediate: true, deep: true } // immediate:初始化时执行;deep:监听数组内部变化
    );

    // picker-view 滚动事件
    const onPickerChange = (e) => {
    const [pIdx, cIdx] = e.detail.value;
    pickerValue.value = e.detail.value;
    currentCityList.value = props.addressList[pIdx].children || [];
    currentAreaList.value = currentCityList.value[cIdx]?.children || [];
    };

    // 确认选择(示例)
    const confirm = () => {
    const [pIdx, cIdx, aIdx] = pickerValue.value;
    const address = {
    province: props.addressList[pIdx]?.name || '',
    city: currentCityList.value[cIdx]?.name || '',
    area: currentAreaList.value[aIdx]?.name || ''
    };
    emit('confirm-address', address);
    showPicker.value = false;
    };
    </script>

    <style scoped>
    /* 样式省略,可沿用之前的样式 */
    .picker-wrapper { height: 500rpx; background: #fff; }
    .picker-view { width: 100%; height: 400rpx; }
    .picker-item { text-align: center; font-size: 28rpx; }
    .loading-tip { text-align: center; padding: 40rpx; color: #999; }
    </style>

    关键解决点说明

  • 父组件核心:

    • 新增 addressDataLoaded 标记,用 v-if 确保子组件只有在数据加载完成后才渲染,从源头避免传空值。
    • 即使接口请求失败,也要标记 addressDataLoaded = true,避免子组件一直不渲染。
  • 子组件核心:

    • 用 watch 监听 props.addressList,并设置 immediate: true(初始化时执行)和 deep: true(监听数组内部变化),确保数据更新时子组件能同步初始化列表。
    • 用 computed 处理 v-model 绑定(uniapp 中子组件支持 v-model 需通过 update:modelValue 事件)。
  • 为什么之前传值为空:

    • 异步请求的特性:父组件渲染 → 子组件初始化(此时接口还在请求,addressList 是空数组)→ 接口返回数据 → addressList 更新,但子组件没监听,所以不知道数据变了。
    • 解决方案要么「等数据加载完再渲染子组件」,要么「子组件监听数据变化」,两者结合最稳妥。
  • 总结

  • 父组件通过 addressDataLoaded 控制子组件渲染时机,避免首次传空值。
  • 子组件通过 watch 监听父组件传值,确保数据更新后同步更新内部列表。
  • 核心逻辑:异步数据要处理「加载状态」,子组件要监听父组件传值变化,这是 Vue 父子组件传异步数据的通用解决方案。
  • 这样修改后,首次打开时子组件要么等数据加载完再渲染,要么能监听到数据更新,彻底解决「传值为空」的问题。

    赞(0)
    未经允许不得转载:171主机测评 » uniapp使用picker-view地址弹框,解决首次加载没有数据展示的问题
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址