一次vue项目中实现展开、收起需求记录
前言
事情是这样的,那是在一个元气不满的周一,发现需求清单上新增了一条需求,这是手里维护中的一个老项目。我点开需求文档链接查看了起来,其中一条内容引起我的注意: 列表添加经营范围字段,描述文字默认展示一行,多余内容显示...省略号;当点击“展开”按钮后,展示全部的经营范围的描述,点击收起时恢复默认效果
我心想这不就是个显示隐藏的效果吗?这么简单的需求,简直撒撒水啦,分分钟搞定你。然而就是这个简单的需求,加上自己的盲目自信浪费了我近1小时。谨以此篇记录自己的学习过程,也希望能帮助到其他同学!
需求拆解
给列表每个item项的指定字段的文字内容后面添加一个展开、收起。这个功能几个注意点如下:
- css文本展开时展示全部内容,收起时展示一行,多余内容...替代
- 展开收起字要跟到文本内容后面
- 每个item的事件互不影响
没带脑子的踩坑
于是我自信的、熟练的写了起来:
- 在dom循环的item上添加添加一行dom,用来展示经营范围字段
- 文本使用span标签,紧跟着直接再使用一个span标签用于展开、收起
- 然后给展开收起dom添加click事件,携带列表v-for循环的index下标
- 事件处理,接收index参数并且赋值给全局变量 activeIndex
- dom上根据全局activeIndex判断是否等于当前index然后加上对文本处理的class
结果发现,当点击第一个dom的展开收起时是没问题的,当点击第二个item的展开收起时第一个item的状态就会跟着改变。很明显这是因为多个item都共用了全局变量activeIndex做判断,所以当activeIndex值发生改变时,列表所有展开收起dom状态都会改变。
带上脑子思索了一下后,这种写法不可行的,这个写法的问题就是造成状态冲突!它适用于单一状态,如:列表单个项目选中效果、多个item只有一个被选择、被高亮的场景。
而现在的需求是:多个item都需要维护自己的一个状态,而且item之间要互不影响!
dom结构、样式
- dmo结构
<!-- item... -->
<div class="businessScope">
<div class="con flex" ref="businessScopeDom">
<span class="name">经营范围:</span>
<span
ref="businessScopeVal"
:class="['value', 'textEllipsis']"
>阿啊阿啊阿啊阿带娃的维阿方法dawdwadwadwadadawdwaa阿啊阿啊阿啊阿带娃的维阿方法dawdwadwadwadadawdwaa阿啊阿啊阿啊阿带娃的维阿方法dawdwadwadwadadawdwaa阿啊阿啊阿啊阿带娃的维阿方法dawdwadwadwadadawdwaa阿啊阿啊阿啊阿带娃的维阿方法dawdwadwadwadadawdwaa阿啊阿啊阿啊阿带娃的维阿方法dawdwadwadw
</span>
<div class="btn">
<span @click="btnClick(index, $event)">展开</span>
</div>
</div>
</div>
- 样式
.flex{
display: flex;
}
.textEllipsis {
width: calc(100% - 140px);
display: inline-block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}
简单的dom结构、class样式,其余具体样式省略
解决方案1(不推荐)
先来看一个不推荐,最笨的方法,那就是直接修改dom的css。在vue项目中也不是不可以,只不过不够优雅。
首先在item的dom中定义ref,然后在click事件中使用$refs获取到dom元素,然后根据点击到的item的index下标来获取具体是哪个dom,然后强制修改文本内容、添加对应class即可。具体代码如下:
btnClick(index, e) {
const li = e.target;
this.activeIndex = index;
if (li.innerText === "展开") {
li.innerText = "收起";
this.$refs.businessScopeVal[index].classList.remove("textEllipsis");
this.$refs.businessScopeDom[index].classList.remove("flex");
} else {
li.innerText = "展开";
this.$refs.businessScopeDom[index].classList.add("flex");
this.$refs.businessScopeVal[index].classList.add("textEllipsis");
}
},
虽然这样是可以实现了需求,但是可以看到相当于是操作了dom,不推荐。
解决方案2(推荐)
值得推荐的方案就是利用vue组件化的特性来实现;也就是把item
这部分迁移出来单独一个组件实现,于是我对老代码进行了重构。
-
创建
ListItem
每个列表项的组件,包含展开、收起状态,props
接收item
数据对象 -
然后在首页dom列表循环中引入
ListItem
组件渲染多个ListItem
将item
数据传入
此时子组件 ListItem
的dom结构、实现逻辑就变得很简单:
<div class="businessScope">
<div :class="['con', textOpen ? '':'flex']">
<span class="name">经营范围:</span>
<span :class="['value', textOpen ? '':'textEllipsis']"
>阿啊阿啊阿啊阿带娃的维阿方法dawdwadwadwadadawdwaa阿啊阿啊阿啊阿带娃的维阿方法dawdwadwadwadadawdwaa阿啊阿啊阿啊阿带娃的维阿方法dawdwadwadwadadawdwaa阿啊阿啊阿啊阿带娃的维阿方法dawdwadwadwadadawdwaa阿啊阿啊阿啊阿带娃的维阿方法dawdwadwadwadadawdwaa阿啊阿啊阿啊阿带娃的维阿方法dawdwadwadw
</span>
<div class="btn">
<span @click="textOpen = !textOpen">{{ textOpen ? '收起' : '展开' }} </span>
</div>
</div>
</div>
可以看到只需要在cliclk事件时对全局变量进行取反即可,这种方式每个列表项的展开/收起状态都由各自的 ListItem
组件管理,避免了状态冲突问题,优雅的解决了这个需求。
这也就解释说明了.vue
组件中的data
数据都应该是相互隔离,互不影响的,组件每复用一次,data
数据就应该被复制一次,之后,当某一处复用的地方组件内data
数据被改变时,其他复用地方组件的data
数据不受影响!
解决方案3
当然还有个方式就是前端拿到数据做循环手动添加一个显示隐藏的控制字段,然后在点击事件发生时修改对应的数据状态从而控制样式的显示隐藏,这个方式有兴趣的可以尝试一下。
css文本一行没撑满就换行问题
实现需求后又发现了一个css文本问题,当前dom中的文本如果包含了英文字母,会出现一行没撑满就换行了,如果是纯文本就没问题。
- 问题如图:
解决:只需要在对应dom加上这个css属性即可解决: word-break: break-all;
- 解决后如图
最终效果
- 收起效果
- 展开效果
总结
很简单的一个需求,其实就是控制文本的显示隐藏。也反应了那句话:需求一句话,开发一整天。 虽然不至于一整天,但是确实有时候看似一句话简单的小功能,开发可能需要好几行代码才能实现的、甚至进行重构,如果稍微复杂点的场景、需求,可能真不是一天,两天就可以完成的了。 还有就是遇到问题、新需求时不要不加思考的就动手,无论简单复杂都需要先分析一下,选择最优解决方案。
如果文章对你有帮助,可以点赞、评论、收藏、转发互动支持哈😀😀😀
评论区评论可以进前端学习摸鱼群哈,一起学习交流!
转载自:https://juejin.cn/post/7396862897764515880