资讯专栏INFORMATION COLUMN

从零开始实现一个Vue级联组件

binaryTree / 2310人阅读

摘要:从零开始实现一个级联组件本文实现级联组件需要用到自定义指令和组件通信相关知识,最好先阅读以下两篇文章自定义指令组件基础与通信一组件简介本文实现的是一个省市县多级联动组件,当组件渲染完成后默认会加载出所有的省名称,当用户点击某个省的名称后,右

从零开始实现一个Vue级联组件

本文实现级联组件需要用到自定义指令组件通信相关知识,最好先阅读以下两篇文章:

Vue自定义指令

Vue组件基础与通信

一、组件简介

本文实现的是一个省、市、县...多级联动组件,当组件渲染完成后默认会加载出所有的省名称,当用户点击某个省的名称后,右边会自动添加一列显示该省下对应的市名称列表,当用户点击某个市后,右边又会自动添加一列显示该市下对应的县名称列表,同时支持级联列表的打开和关闭。

二、组件实现设计思路

① 组件所需要的数据,数据结构非常简单,对象里面只有两个属性,一个是label(标签名),如果当前标签下还有子标签,则会多一个children属性,children属性值为一个数组每个数组元素为其下的一个子标签

// data.json, 为避免数据占用太多篇幅,这里只列举了一条数据

[
    {
        "label": "江西",
        "children": [
            {
                "label": "赣州",
                "children": [
                    {
                        "label": "全南县"
                    },
                    {
                        "label": "龙南县"
                    }
                ]
            }
        ]
    }
]

② 我们的级联组件分为上下两部分组件,上部分显示用户选择的路径,下部分显示用户选择列表,同时支持点击级联组件的上部分可以实现下半部分的打开和关闭,点击组件外面关闭组件的下半部分,这里需要用到v-click-outside指令,这里自定义指令的代码就不再重复,请参考Vue自定义指令
// Cascader.vue 新建一个Cascader.vue组件



注意到组件中有一个selectedItems数据,这是一个数组,默认值为空数组,因为当级联组件渲染完成后,默认用户是没有点击选择其中任何一项的,只有当用户点击了某一项后,才会将点击的这一项添加到selectedItems数组中,其就是记录用户的选择项。这里需要理解清楚选择项的概念:
比如我们的级联组件有三列,省、市、县三列,结合上面的数据结构,整个省是一个大对象,即省对象,省对象中有children属性,里面包括多个子对象,即市对象,市对象中又包括children属性,里面包括多个子对象,即县对象,县对象中不再有children了,具体表示就是:
省对象:
{ "label": "江西", children: [省略...]}
市对象:
{ "label": "赣州", children: [省略...]}
县对象:
{ "label": "全南县"}
当用户点击第一列,那么就将整个省对象添加到selectedItems数组中的第一项位置,当用户接着点击了第二列,如省对象中的label为"赣州"的市对象,则将整个市对象添加到selectedItems数组中的第二项位置,当用户又点击了第三列,如"赣州"市对象下的label为"全南县"的县对象,则将整个县对象添加到selectedItems数组中的第三项位置,这样selectedItems数组中就保存了用户选择的三列数据了,然后将三列数据中的label取出通过"/"连接起来,即用户的选择路径"江西/赣州/全南县"。

③ 接下来就是考虑组件拿到数据后,如何渲染的问题了
这里需要用到组件内递归组件,我们可以左右两列抽象成一个多带带的组件CascaderItem.vue,但是右边这一列会不会显示,得看用户有没有选择左边的项,如果点击了左边的项则显示右边的列,如果没有点击左边的项则不显示右边的列。

还是以省、市、县三列为例,中间的市这一列,既是省的右列,也是县的左列,我们已经将左右两列抽象了一个多带带的CascaderItem组件,关键是理解省这一列的右边部分到底是什么?,从表面上看,省这一列的右边就是一个市列,但是如果右边仅仅是市这一列的话,那么当用户点击市这一列中的某项的时候,就无法显示市右边的县列了,所以省这一列的右边其实又是一个CascaderItem组件,只有这样点击市列中的某一项的时候,其右边的县列才会显示出来。所以我们需要在CascaderItem组件内递归自己,而组件内递归自己,那么必须给组件添加name属性,即给组件取一个名字,如:

// CascaderItem.vue



CascaderItem组件组件的渲染数据来自于顶层父组件Cascader中的selectedItems数据,因为用户点击了左侧列中的项后,会将点击的item项添加到selectedItems中,selectedItems中数据变化之后才会显示右侧的列。
CascaderItem组件需要接收一个level属性,用来记录当前CascaderItem组件所属层级,即第几列,为了方便,我们从0开始表示第一列,即第一层所以Cascader.vue中level传入0,后面没加一层level会加1,如:

// 补全上面的Cascader.vue,渲染出下半部分

CascaderItem组件的左边部分都监听了一个click事件,当用户点击左边的列选项后,需要将当前所在level和item对象数据传递到Cascader父组件中的selectedItems数组中,以便获取用户的选择路径,因为单向数据流,子组件不能直接修改父组件传递过来的数据,所以需要去父组件中修改数据,这里以事件的方式通知顶层父组件自己更新数据

// CascaderItem.vue给CascaderItem组件添加一个select()方法

export default {
    methods: {
        select(item) { // 处理CascaderItem组件内左侧列点击事件,item为当前点击的对象
            // 向上一级发射一个change事件,通知上层进行修改,并将当前点击的层级level和item传递过去
            this.$emit("change", {level: this.level, item: item});
        }
    }
}
由于CascaderItem是递归调用的,所以现在的组件调用关系为: Cascader --> CascaderItem --> CascaderItem --> CascaderItem --> ......
顶层父组件为Cascader,所以CascaderItem也可能是CascaderItem的父组件CascaderItem组件自身也需要监听change事件,主要就是负责将数据改变信号传递到Cascader顶层父组件上,如:

// CascaderItem.vue给CascaderItem组件添加一个change事件处理方法

export default {
    methods: {
        change(newValue) { // 向顶层传递数据改变信息
            this.$emit("change", newValue);
        }
    }
}
顶层父组件Cascader接收到数据改变信号后,就需要改变selectedItems数据了,即将用户的选择项添加到对应的位置,如:

// Cascader.vue 添加change事件处理函数

export default {
    methods: {
        change(newValue) {
            this.selectedItems.splice(newValue.level, 1, newValue.item); // 替换当前点击位置信息
            this.selectedItems.splice(newValue.level + 1); // 删除当前点击位置之后的数据
        }
    }
}
Cascader组件除了替换掉指定level中的数据外,还需要将当前level之后的数据删除掉,否则当前level之后的数据还在,导致右侧路径仍然保留而显示不一致。

至此,一个简单的级联组件就实现了,可以在App.vue中直接使用,如:
// App.vue


三、总结
整个Cascader组件设计思路就是: 在顶层父组件Cascader中添加一个selectedItems数组,用于保存用户点击的level层级(列序号)和对应的item对象,同时用于生成用户的选择路径,当用户点击了CascaderItem组件的左侧列中某项后,通过层层传递事件的方式通知顶层父组件Cascader对其数据进行更新,顶层父组件Cascader更新数据后,CascaderItem组件从selectedItems中取出对应level的item对象,然后获取item的children并遍历显示右侧列

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/110003.html

相关文章

  • 第十集: 从零开始实现一套pc端vue的ui组件库( 计数器组件 )

    摘要:第十集从零开始实现计数器组件本集定位听到计数器这个名字很多人是不是一瞬间没有什么印象毕竟这个组件用的比较少就是那种左边一个右边一个控制某些数量的时候才会用到比如我之前做的商城小程序只有下单页面的规格弹出框里面才有他的身影如果是涉及到处理商 第十集: 从零开始实现( 计数器组件 ) 本集定位: 听到计数器这个名字很多人是不是一瞬间没有什么印象, 毕竟这个组件用的比较少,就是那种左边...

    Sanchi 评论0 收藏0
  • 第十集: 从零开始实现一套pc端vue的ui组件库( 计数器组件 )

    摘要:第十集从零开始实现计数器组件本集定位听到计数器这个名字很多人是不是一瞬间没有什么印象毕竟这个组件用的比较少就是那种左边一个右边一个控制某些数量的时候才会用到比如我之前做的商城小程序只有下单页面的规格弹出框里面才有他的身影如果是涉及到处理商 第十集: 从零开始实现( 计数器组件 ) 本集定位: 听到计数器这个名字很多人是不是一瞬间没有什么印象, 毕竟这个组件用的比较少,就是那种左边...

    Kerr1Gan 评论0 收藏0
  • 第二集: 从零开始实现一套pc端vue的ui组件库(icon组件)

    摘要:第二集从零开始实现组件本集定位这套组件我本来是先从做的但是我发现每个组件都要用到这个组件如果没有他很多组件没法扩展而且本身不依赖其他组件所以还是先把它作为本篇文章的重点吧组件读过源码的同学都知道他们选择的是字体图标的方式来做组件的而我的这 第二集: 从零开始实现(icon组件) 本集定位: 这套ui组件我本来是先从button做的, 但是我发现每个组件都要用到icon这个组件, 如...

    dack 评论0 收藏0
  • 第二集: 从零开始实现一套pc端vue的ui组件库(icon组件)

    摘要:第二集从零开始实现组件本集定位这套组件我本来是先从做的但是我发现每个组件都要用到这个组件如果没有他很多组件没法扩展而且本身不依赖其他组件所以还是先把它作为本篇文章的重点吧组件读过源码的同学都知道他们选择的是字体图标的方式来做组件的而我的这 第二集: 从零开始实现(icon组件) 本集定位: 这套ui组件我本来是先从button做的, 但是我发现每个组件都要用到icon这个组件, 如...

    wua_wua2012 评论0 收藏0

发表评论

0条评论

binaryTree

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<