在鸿蒙应用开发中,省市区三级联动是一个常见的功能需求,如收货地址选择、用户信息注册等场景。本文将基于ArkTS语言,详细介绍如何实现一个高效、可复用的省市区三级联动选择器

功能特点

•支持省、市、区三级数据联动

•平滑的滚动选择体验

•高性能的数据加载与渲染

•灵活的定制化选项

实现原理

三级联动选择器的核心原理是基于数据驱动视图更新。当用户选择省份时,城市列表会根据所选省份动态更新;同样,选择城市后,区县列表也会相应变化。

关键技术点

•使用TextPicker组件实现滚动选择

•采用@State装饰器管理组件状态

•通过异步数据加载提升用户体验

•利用函数防抖优化性能

基础实现

数据结构定义

首先需要定义省市区数据的结构模型:

// 省份信息模型
class ProvinceBean {
  id: number;
  code: string;
  label: string;
}
​
// 城市信息模型  
class CityBean {
  id: number;
  province_code: string;
  code: string;
  label: string;
}
​
// 区县信息模型
class DistrictBean {
  id: number;
  city_code: string;
  code: string;
  label: string;
}
[4](@ref)
​
​
​
​

基础组件框架

@Entry
@Component
struct AreaPickerPage {
  // 控制弹窗显示/隐藏
  @State isPickerVisible: boolean = false;
  
  // 省市区数据数组
  @State provinceData: ProvinceBean[] = [];
  @State cityData: CityBean[] = [];
  @State districtData: DistrictBean[] = [];
  
  // 当前选中的索引
  @State selectedProvinceIndex: number = 0;
  @State selectedCityIndex: number = 0;
  @State selectedDistrictIndex: number = 0;
​
  build() {
    Column() {
      // 页面内容
      Button('选择地址')
        .onClick(() => {
          this.isPickerVisible = true;
        })
    }
    .bindSheet(this.isPickerVisible, this.pickerBuilder(), {
      height: 300
    })
  }
​
  @Builder
  pickerBuilder() {
    // 选择器内容
  }
}
[1](@ref)
​
​
​
​

完整实现方案

数据加载与初始化

应用启动时加载初始数据:

async aboutToAppear() {
  // 获取省份数据
  const provinces = await this.loadProvinceData();
  this.provinceData = provinces;
  
  if (provinces.length > 0) {
    // 获取第一个省份的城市数据
    const cities = await this.loadCityData(provinces[0].code);
    this.cityData = cities;
    
    if (cities.length > 0) {
      // 获取第一个城市的区县数据
      const districts = await this.loadDistrictData(cities[0].code);
      this.districtData = districts;
    }
  }
}
[1](@ref)
​
​
​
​

联动选择器实现

@Builder
pickerBuilder() {
  Column() {
    // 顶部操作栏
    Row() {
      Text('取消')
        .onClick(() => {
          this.isPickerVisible = false;
        })
      
      Blank()
      
      Text('确定')
        .onClick(() => {
          this.onAddressSelected();
          this.isPickerVisible = false;
        })
    }
    .width('100%')
    .padding(20)
​
    // 三级选择器
    Row() {
      // 省份选择
      TextPicker({ range: this.getProvinceNames(), selected: this.selectedProvinceIndex })
        .onChange((value: string, index: number) => {
          this.onProvinceChange(index);
        })
        .layoutWeight(1)
​
      // 城市选择  
      TextPicker({ range: this.getCityNames(), selected: this.selectedCityIndex })
        .onChange((value: string, index: number) => {
          this.onCityChange(index);
        })
        .layoutWeight(1)
​
      // 区县选择
      TextPicker({ range: this.getDistrictNames(), selected: this.selectedDistrictIndex })
        .layoutWeight(1)
    }
    .height(200)
  }
}
[7](@ref)
​
​
​
​

联动逻辑处理

// 省份变更处理
async onProvinceChange(index: number) {
  // 使用防抖避免频繁触发
  if (this.debounceTimer) {
    clearTimeout(this.debounceTimer);
  }
  
  this.debounceTimer = setTimeout(async () => {
    this.selectedProvinceIndex = index;
    const province = this.provinceData[index];
    
    // 加载城市数据
    this.cityData = await this.loadCityData(province.code);
    this.selectedCityIndex = 0;
    
    if (this.cityData.length > 0) {
      // 加载区县数据
      this.districtData = await this.loadDistrictData(this.cityData[0].code);
      this.selectedDistrictIndex = 0;
    }
  }, 300);
}
​
// 城市变更处理
async onCityChange(index: number) {
  this.selectedCityIndex = index;
  const city = this.cityData[index];
  
  // 加载区县数据
  this.districtData = await this.loadDistrictData(city.code);
  this.selectedDistrictIndex = 0;
}
[1](@ref)
​
​
​
​

性能优化

函数防抖处理

在处理快速滚动时,通过函数防抖避免频繁请求:

@State debounceTimer: number = -1;
​
// 防抖函数
debounce(func: Function, delay: number) {
  return (...args: any[]) => {
    if (this.debounceTimer !== -1) {
      clearTimeout(this.debounceTimer);
    }
    this.debounceTimer = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}
[1](@ref)
​
​
​
​

数据缓存策略

实现数据缓存避免重复请求:

// 数据缓存对象
private static dataCache: Map = new Map();
​
// 带缓存的数据加载
async loadDataWithCache(url: string): Promise {
  if (AreaPickerPage.dataCache.has(url)) {
    return AreaPickerPage.dataCache.get(url);
  }
  
  const data = await this.requestData(url);
  AreaPickerPage.dataCache.set(url, data);
  
  return data;
}
[4](@ref)
​
​
​
​

高级功能扩展

自定义样式配置

支持深色模式适配和自定义主题:

@Extend(TextPicker)
function customPickerStyle() {
  .selectedTextStyle({
    color: $r('app.color.picker_selected'),
    font: { size: 18, weight: FontWeight.Medium }
  })
  .textStyle({
    color: $r('app.color.picker_normal'),
    font: { size: 16 }
  })
}
[7](@ref)
​
​
​
​

云端数据集成

支持从云端动态加载地址数据:

// 调用云函数获取省份数据
async loadProvinceFromCloud() {
  try {
    const functionCallable = agconnect.function().wrap("province-query-$latest");
    const result = await functionCallable.call({});
    this.provinceData = result.getValue().result;
  } catch (error) {
    console.error('Failed to load province data:', error);
  }
}
[3,4](@ref)
​
​
​
​

组件封装与复用

将三级联动选择器封装为独立组件,便于复用:

@Component
export struct AreaPickerComponent {
  @Prop onConfirm: (province: ProvinceBean, city: CityBean, district: DistrictBean) => void;
  @Prop onCancel: () => void;
  
  @Link selectedProvince: ProvinceBean;
  @Link selectedCity: CityBean; 
  @Link selectedDistrict: DistrictBean;
​
  build() {
    // 组件实现
  }
}
​
​
​
​

调用方式:

// 在父组件中使用
AreaPickerComponent({
  onConfirm: (province, city, district) => {
    console.log(`选中: ${province.label} ${city.label} ${district.label}`);
  },
  onCancel: () => {
    console.log('选择取消');
  }
})
[2,7](@ref)
​
​
​
​

总结

通过ArkTS实现省市区三级联动选择器,关键点在于:合理的状态管理高效的数据加载流畅的联动交互。本文介绍了从基础实现到高级优化的完整方案,开发者可以根据实际需求进行调整和扩展。