如果我掏出下图,阁下除了私信我加入学习群,还能如何应对?
正文开始
- 前言
- 一、历史问题
- 二、通过监听select事件实现全选不靠谱!!!
- 三、 通过外部事件控制树选择组件
- 四、render函数创建组件
- 4.1 不得不说的h函数
- 4.2 如果条件允许,请使用jsx
- 总结
前言
因为种种历史原因,我们一直选择的vue前端框架是iview,而非element,在老版的iview中,treeselect(树选择组件)一直都是半成品,后来团队买了iview pro版,树选择组件虽然能够使用,但功能仍显单一,缺少全选功能。
现在项目要求实现全选,就只能自己动手了。
心情烦躁者,会觉得一多半文章在讲废话,请直接下拉到最后查看实现方式。
一、历史问题
研究早期iview版本的treeselect源码,我们可以将它和数据有关系的部分分为两类:
- 树形图数据
- select选项数据
select选项数据其实又可以分为两部分:
3. 下拉框中选项部分;
4. input中的tag标签显示部分。(看着像input,其实是渲染成div了,为了便于理解,就这么称呼了)
如图所示:
这几部分数据大致的逻辑如下:
1.渲染树形图数据,在下拉框中形成树形图。
2.点击树形图的数据,会触发select的方法,选中点击的数据
3.select方法被触发后,会触发input方法,修改tag标签的显示。(这个是真的input方法,形如:vm.$emit(‘input’,xxxxx))
逻辑很简单,不简单的是,当数据增加、删除、改变时,这三个数据逻辑之间互相监听,来回改变,最终为了实现三项数据的联动,导致数据变化监听十分复杂。
这就导致我们如果要实现全选功能,最好就不要再去通过监听select事件来实现。
二、通过监听select事件实现全选不靠谱!!!
iview pro版本是采用函数组件的方式,查看它源码后,会发现虽然代码清爽了许多,但是三者之间还是联动。
所以我不想再采用监听select的方式。原因有二:
- 监听select,触发某个条件后再手动为select赋值,会继续触发监听,形成死循环。
- 上面的问题可以解决,但代码过多,并无必要,而且需要修改源码,不利于后续框架升级。
切记:研究新旧版select代码的过程绝对不舒服,希望大家有所了解就行,不要好奇去看它代码,会被各种监听搞吐的!!!
三、 通过外部事件控制树选择组件
既然不能监听内部的select事件,那我通过和树组件并不想关的一个事件,调用select提供的方法,是不是就能避免修改源码,并且不会形成死循环。
尝试后,发现是可以的。具体操作如下:
- 创建一个普通的树组件,其中树形图数据为treeData,组件的v-model绑定的数据为:argStrList,代码如下
<TreeSelect ref="treeSelect" :max-tag-count="3" multiple v-model="argStrList" :data="treeData"/>
- 在组件毫不相关的地方,创建一个button,点击事件中修改argStrList,并做个延迟,方便我们有时间打开下拉框查看变化。代码如下:
setTimeout(()=>{
this.argStrList=allValueArr
},3000)
allValueArr是我创建的一个value值数组,具体是什么,根据各自项目情况而定。
- 打开下拉框后,发现过了两三秒,下拉框中对应的选项被选择,input中的tag也相应维护,说明外部事件控制select来触发创建,并不会产生额外的副作用。
但是这种方式也实在太不优雅了,所以就想到在下拉框里渲染元素和事件,但是并不和select本身的元素和事件关联。treeselect组件并没有直接提供在树形图里面添加其它元素的方式。但树选择组件是基于tree组件的,tree组件提供了render函数自定义元素内容。
所以我们要做的就是在tree树形图中,创建一个能被我们自由控制的元素和事件。
四、render函数创建组件
4.1 不得不说的h函数
不得不说,h函数很多场景很好用,但在反人类的道路上一去不返,我并不支持这种写法,所以只列代码,不讲解:
render: (h, { root, node, data }) => {
return h('span', {
style: {
display: 'inline-block',
width: '100%'
}
}, [
h('span', [
h('span', data.title)
]),
h('span', {
style: {
display: 'inline-block',
float: 'right',
marginRight: '32px'
}
}, [
h('Checkbox', {
style: {
},
on: {
'on-change': () => {
this.argStrList.push('1400344119453511682')
}
}
}),
h('span','全选')
])
]);
},
4.2 如果条件允许,请使用jsx
Babel版本3.4.0开始已经支持jsx,相当老的版本了,如果项目里插件版本过低,请升级。
树形图root节点的全部代码如下:
let root = {
id: '0',
name: '组织架构树',
type: 0,
render:(h,{root,node,data})=>{
const key='value' || "id"
const allValueArr=_.map(_.map(root,'node'),key)
const checkChange=(e)=>{
if(e){
this.argStrList=allValueArr
}else{
this.argStrList=[]
}
}
return(
<div>
<Checkbox vOn:on-change={(e)=>checkChange(e)}>{data.title}</Checkbox>
</div>
)
}
}
这段代码主要就是在root节点上渲染了一个checkbox,并添加事件。如图:
注意点:
- jsx语法请查看github官网文档;
- _.map是loadash提供的api,作用是把所有树形节点的value取出来组成数组allValueArr;
总结
- treeSelect组件内部各种互相监听太复杂了,很容易动一处爆两处bug,所以慎动。
- 内部监听事件解决不了,就做个外部事件。然后用黑科技,把外部事件做到tree里,看起来好像是本身的一部分一样。这样做出来的全选功能,就只在treedata数据里增加一个属性,对项目代码和treeselect组件代码影响都是最小的。
- treeselect这种实现方式应该引以为戒,太难维护了。我虽然没有实操,但是这种很多类数据之间有关联的场景,应该尽可能用单例模式,创建个类或者自执行的闭包,相当于模拟一个三两个组件共享的小型状态管理工具,把数据维护到内存。尾大不掉还是确实没办法这样去实现,不得而知。
- 后续如果想做级联选择,就是改变on-change事件里的算法即可