前言
- 级联选择不少组件都有,但是如果是选地区,大部分的组件需要提供的数据就变得要求数据带children以获得下级选项。如果不能复制粘贴,那还不如自己写个地区级联选择组件。
数据
- 我在网上翻到一个数据接口提供的中国省市区数据接口地址。
- 这个接口地址的数据就是key value下来,毫无children分级。
- 此次就以这个接口为例制作。
思路
- 一开始制作这个的思路是懵逼的,因为难点还是比较多的,首先需要解决第一个难点。
- 第一个难点就是这个接口的级联关系到底是什么?如果连关系都理不清,更别说制作了。
- 先看数据前面几个:
"110000":"北京市",
"110101":"东城区",
"110102":"西城区",
"110105":"朝阳区",
"110106":"丰台区",
"110107":"石景山区",
"110108":"海淀区",
"110109":"门头沟区",
"110111":"房山区",
"110112":"通州区",
"110113":"顺义区",
"110114":"昌平区",
"110115":"大兴区",
"110116":"怀柔区",
"110117":"平谷区",
"110118":"密云区",
"110119":"延庆区",
"120000":"天津市",
"120101":"和平区",
"120102":"河东区",
"120103":"河西区",
"120104":"南开区",
"120105":"河北区",
"120106":"红桥区",
"120110":"东丽区"
……
"510000":"四川省",
"510100":"成都市",
"510104":"锦江区",
"510105":"青羊区",
"510106":"金牛区",
"510107":"武侯区",
"510108":"成华区",
"510112":"龙泉驿区",
"510113":"青白江区",
"510114":"新都区",
"510115":"温江区",
- 可以发现这个数据有下面几个特性:
- 前2位表示哪个省,中间2位表示哪个市,最后2位表示哪个区。
- 但是直辖市会有点特殊,直辖市没有市这个键,比如北京如果算省的话是110000,如果是市的话那么区应该是1100??,但是没有1100??,所以这里看见直辖市需要变为1101??才是所属直辖市的区。
- 然后是第二个难点,怎么变为级联数据?
- 一般蛮力操作是制作成像一开始说的那些组件一样带children层级的进行渲染。我后来想了个办法,使用正则,直接先提取省,用户选了哪个,再进行下一轮选市。选了市,再进行选区。这样这个难点就解决了。
效果
代码
- 使用需要传几个参数,display用来控制显示,还需要把控制显示的方法传给它,这样用户点击遮罩层或者选择完组件后,组件内可以关闭。最后还要传个回调函数,用来获取用户最后选定的值。
<CityPicker display={cityCom} setState={setCityCom} callbackFunc={CityPickerCallback}></CityPicker>
import React, { useState } from 'react';
import './index.less'
import cityData from '@/assets/city.json'
interface Props{
display:boolean;
setState:(value: React.SetStateAction<boolean>) => void
callbackFunc:(value:string)=>void
}
interface RenderState{
nextLis:Array<string>
open:boolean
}
function CityPicker(props:Props){
const [secRender,setsecRender]=useState<RenderState>({
nextLis:[],
open:false
})
const [thirdRender,setThirdRender]=useState<RenderState>({
nextLis:[],
open:false
})
const [userPick,setUserPick]=useState<Array<string>>(['请选择','请选择','请选择'])
const firstReg=/^[0-9][0-9]0000$/
const toChangeNext=(item:string)=>{
const itemHead=item.slice(0,2)
const secReg=new RegExp(`^${itemHead}[0-9]([1-9]|(?!0)[0-9])00$`)
const nextLis = Object.keys(cityData).filter(it=>secReg.exec(it))
if(nextLis.length===0){
nextLis=[item]
}
setUserPick([item,'请选择','请选择'])
setsecRender({
nextLis,
open:true
})
setThirdRender({
nextLis:[],
open:false
})
}
const toChangeThird=(item:string)=>{
const itemHead = item.slice(0,4)
const cityKeys = Object.keys(cityData)
const thirdReg = new RegExp(`^${itemHead}[0-9]([1-9]|(?!0)[0-9])$`)
const nextLis =cityKeys.filter(it=> thirdReg.exec(it))
if(nextLis.length===0){
const path = item.slice(0,2)
const sReg = new RegExp(`${path}01[0-9]{2}`)
nextLis=cityKeys.filter((it)=>sReg.exec(it))
}
if(nextLis.length===0){
console.error('请检查数据')
}
let lis = userPick
lis[1]=item;
lis[2]='请选择'
setUserPick(lis)
setThirdRender({
nextLis,
open:true
})
setsecRender({
...secRender,
open:false
})
}
const toFinal= (item:string)=>{
const lis = userPick
lis[2]=item
setUserPick([...lis])
let fivalue= lis.reduce((prev,next)=>{
if(prev===cityData[next])return prev;
prev =prev+ cityData[next]
return prev
},'')
props.callbackFunc(fivalue)
props.setState(false)
}
return(
<>
<div className='city-picker-container'
style={{
animation:props.display?'conainerSlide 0.1s linear':'',
display:props.display?'':'none'
}}>
<div className="city-picker-main">
<div className='city-picker-title'>所在地区</div>
<div className='city-picker-display'>
{
userPick.map((item,index)=>{
return (
<span key={index}>
{item==='请选择'?'请选择':cityData[item]}
</span>
)
})
}
</div>
<div className='city-picker-x'
>
{
Object.keys(cityData).filter((item)=>(firstReg.exec(item))).map((item)=>{
return(
<div key={item} onClick={()=>toChangeNext(item)}>
{cityData[item]}
</div>
)
})
}
</div>
<div className='city-picker-y'
style={{display:secRender.open?'':'none'}}
>
{
secRender.nextLis.map((item)=>{
return(
<div key={item} onClick={()=>toChangeThird(item)}>
{cityData[item]}
</div>
)
})
}
</div>
<div className='city-picker-z'
style={{display:thirdRender.open?'':'none'}}
>
{
thirdRender.nextLis.map((item)=>{
return(
<div key={item} onClick={()=>toFinal(item)}>
{cityData[item]}
</div>
)
})
}
</div>
</div>
</div>
<div className='city-picker-mask'
style={{display:props.display?'':'none'}}
onClick={()=>props.setState(false)}></div>
</>
)
}
export default CityPicker
- 这里3次选择用了列表的3项表示。这里就有个坑,就是如果用数组直接setState并不会刷新页面。可以试试把注释相当于强制刷新那换成正常的setState写法。会发现state改变但是页面并没有刷新的神奇现象。所以这时需要让react发现我们传给它的列表已经完全换掉了,不再是老的状态了。
- 如果有读者没明白的等我把项目写完发出来下载下来看。