【React】手写地区级联选择组件

前言

  • 级联选择不少组件都有,但是如果是选地区,大部分的组件需要提供的数据就变得要求数据带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发现我们传给它的列表已经完全换掉了,不再是老的状态了。
  • 如果有读者没明白的等我把项目写完发出来下载下来看。
发布了166 篇原创文章 · 获赞 10 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/yehuozhili/article/details/105068664