返回

省市区联动组件开发攻略

前端

如何编写省市区联动组件

省市区联动这种插件应该很多人用过,在我第一次写这种功能的时候,第一时间就是找插件,尤其是移动端,对当时的我来说从来没有想过自己写。

最近看了同事写的省市区的组件,发现几乎所有的picker组件都是根据手指移动距离来滑动,但是有个问题,就是我快速的滑动一下,组件是没办法像原生scroll那样回弹的,但是会重新定位到距离最近的某一项,如果此时有3列,那点击列表的时候肯定会很不舒服。

接下来讲讲我们是怎么做的,包括滚动组件的原理,省市区数据的组织方式。

滚动组件的原理

用react写滚动组件,大家想到的一般是onTouchStart onTouchMove onTouchEnd这样的原生事件,但是react封装的TouchEvent API有点奇怪,触摸事件回调的参数是一个对象,里边就有很多常见的方法和属性,比如clientX,clientY。

刚开始我天真的以为官方已经把这些事件想好了,所以一开始就用了官方的API,然后问题就出现了。

onTouchStart和onTouchMove事件是源源不断触发的,如果是快速的滑动的话,可能会在很短的时间内触发非常多的onTouchMove事件,这样我们拿到这些事件后,一个个处理的话,必然会造成性能问题,事实上也确实是如此。

后来同事发现了这个问题,经过一番思考后决定用requestAnimationFrame来控制onTouchMove事件的触发次数,并且在其中做了一些复杂的运算,实现了回弹效果,同时解决了性能问题,这种思路还是比较常见的,大家也可以借鉴下。

省市区数据的组织方式

前端获取省市区的数据来源很多,比如:

  • 接口
  • json文件
  • 本地存储

为了省事,我一开始是用了后两个方案。

用json文件的话可以直接将json文件拖进编辑器,用本地存储的话就 需要先把一个json文件上传到服务器上,然后通过接口请求的方式拿到数据,前端获取省市区数据的方式可能还有很多,这里就不细说了。

无论如何,拿到的数据都是一个json文件,格式一般是:

{
    "省份": [{
        "省份名称": "",
        "城市": [{
            "城市名称": "",
            "区县": [{
                "区县名称": ""
            }]
        }]
    }]
}

一开始我看这数据的格式感觉非常坑,因为在获取一个省份的子项的时候需要遍历很多层,所以我一开始自己写了一个比较扁平的json格式,大概是这样:

[
    {
        "value": 1,
        "label": "北京",
        "children": [
            {
                "value": 11,
                "label": "北京市",
                "children": [
                    {
                        "value": 1101,
                        "label": "东城区"
                    },
                    {
                        "value": 1102,
                        "label": "西城区"
                    }
                ]
            }
        ]
    }
]

这样虽然数据结构看起来很直观,但是由于省市区的子项过多,导致整个数组非常大,还很容易造成数据冗余。

后面又查了官方文档,发现他们使用的是一个叫做flat-map的方法,这个方法可以把多维数组“拉平”成一维数组,还能自动给子项加上一个父节点的标识,而这就是我们想要的:

[
    {
        "value": 1,
        "label": "北京",
        "parent": null
    },
    {
        "value": 11,
        "label": "北京市",
        "parent": 1
    },
    {
        "value": 1101,
        "label": "东城区",
        "parent": 11
    },
    {
        "value": 1102,
        "label": "西城区",
        "parent": 11
    }
]

省事的人可以直接用flat-map这个方法,但是要手写的话,也可以写个递归函数,不过手写的话就需要注意一下递归的出口,否则会出问题的。