我是这么设计业务[封装组件]

7,466 阅读8分钟

前言

hello,大家好!我是lin

今天又想和大家分享封装组件这件事。在开发业务需求的过程中,我们可能会遇到一个全新的业务需求,也可能会遇到一个原来已有的功能去迭代新的功能。

显然,如何去设计一个全新的业务需求值是值得我们去思考的。

同样,在一个原有的功能基础上去开发迭代也是值得去思考的。

而不仅仅只是command+ccommand+v了!

这篇文章也算是复盘总结自己对于需求如何去设计组件。

写文总结不易,若有更好的设计和思想,期待一起碰撞。

需求&思考

需求的拟稿:

当我拿到需求拟稿的时候,我先看了element-ui的级联组件不能满足业务,在此基础上改造也相对麻烦。所以最终我还是打算自己封装一个级联组件。

接下来,我根据需求思考要实现的功能如下:

1.支持用户通过搜索类目,进行选择。

2.支持用户通过展开类目点击选择。(ps:点击第一个面板时将它子节点的第一级面板全部展开)

3.初始化状态,将默认展开第一个节点的第一位的所以节点。

4.支持正选和反选节点。

5.面板的每个搜索框可控制该面板的搜索。

如何实现需求

明确好自己要实现的需求功能之后,我所认为的是先不要急于编写代码,应该再做一个深入的分析思考。

我先从数据结构入手,这个数据结构是树形的。类似这样的:

const data=[
	{
    	text: "指南",
    	id: 2,
        children: [
          {
            text: "设计原则",
            id: 2.1,
            children: [
              {
                text: "导航1",
                id: 2.112,
              },
              {
                text: "导航2",
                id: 2.113,
              },
              {
                text: "导航3",
                id: 2.114,
              },
            ],
          },
          {
            text: "设计原则2",
            id: 2.2,
            children: [
              {
                text: "导航2",
                id: 2.21,
              },
            ],
          },
    ],
  },
]

依据这样的树形数据格式,我可以明确这需要一个递归组件来实现。

但是现在有个关键的问题出现了,模板如何渲染这样的数据格式?

  • 方案1:在设计这个递归组件时,是要把每一个node节点当成一个递归组件,比如遍历一个node节点,若有children属性的话,那么就一直递归下去,直到遇到节点没有children属性时停止。然后再遍历下一个节点...

  • 方案2:把递归组件拆成左右板块,左边先遍历同级的node节点,右边的数据渲染是通过选中上一级的node节点来控制下一级的node节点。(ps:同级的node节点遍历)

其实总结起来就是:要深度遍历还是广度遍历呢?(ps:最终的方案留个疑问,下文讲述...)

设计组件

这里是通过一个递归组件CascaderItem和搜索过滤filterItem组件以及一个父组件Cascader来完成需求。

先从布局开始

拟稿组件的布局是左右面板,开发这个组件我是使用flex布局,我最终是使用方案2来遍历数据渲染。 方案1在功能的实现上是没有问题的,但是就布局来看会有一定的缺陷的,因为方案1是根据一个node节点作为一个面板。

方案2的组件模板:

left面板是同级节点的循环遍历,right部分是组件自身引用自身,那么就成了一个递归组件。

组件属性的设计

这个组件的属性设计,我是通过props属性进行传递的。

递归组件CascaderItem的属性有:optionslevel(ps:表示当前是哪一级)、changeValToStrisCheckboxisInputreverseChoice

而父组件Cascader的属性除了上面的属性之外还有:filterablevalue(ps:双向绑定,下文讲述)、iptProps

具体各个属性的含义可查看源码注释,这里不再做讲述了...

递归组件实现思路

这里是通过一个计算属性showRightItem来控制是否需要显示隐藏子节点的面板(ps:递归的终止的条件)。而这个计算属性依赖于一个响应式数据selected数组(ps:被选中的节点)。通过判断选中的该级节点是否有children属性来控制showRightItem返回值。

这个selected是通过父组件Cascader传给子组件CascaderItem。在这里我是通过selected.sync传递属性,并且当用户点击node节点时可以触发update:selected事件让父组件的数据发生改变。

捕获用户行为

当用户点击node的节点时,不仅仅要触发update:selected事件的。要在这个用户行为的函数上处理一些其他的逻辑。等所以的逻辑都完成之后再触发事件通知父组件。

涉及到的逻辑如下:

1.判断处理用户点击行为是正选还是反选。

2.操作selected数组,该级节点的下一级节点数据删除。因为上一级节点被切换了,那么下一级节点要被删除,实现切换数据视图一致的效果。

3.选中的该级节点是否有children属性,如果有则需要将下级的第一位pushselected数组,直到下级的第一位没有children属性为止。

当用户在每一级进行搜索的时,触发的是input事件。要在这个用户行为上做当前级下的数据过滤。

搜索当前级下的数据时,下一级的面板的数据是无法预知的。为了解决搜索的父节点的数据与当前子面板的数据不相关联(ps:它可能还停留在上一个节点的子节点数据),让用户感到困惑。

这里是通过一个开关进行控制,在用户搜索时将下一个面板隐藏。等到用户确定搜索的目标时再进行展开。

好了,到这里递归组件大致的逻辑完成了。具体的实现可查看源码...

父组件的实现思路

父组件是暴露给用户使用的组件。它类似总池子将任务分配给不同的子组件。这里通过用户传来的filterable属性来确定是否是使用递归组件CascaderItem还是filterItem组件。

秉承着高内聚低耦合的思想,组件对外暴露onChange事件以及v-model属性。

当选中的节点发生变化时触发onChange事件。v-model属性实现数据的双向绑定,内部触发input事件及时更新用户绑定的数据。

父组件类似总池子,它还负责处理一些数据。主要涉及到的逻辑如下:

1.初始化数据,将树形结构数据新增pathObjchecked属性。(ps:pathObj属性是用来记录上下级节点的关系,checked这个属性是服务于递归组件的checkbox框)

2.将树形数据扁平化,为搜索时提供总数据。

3.初始化selected数组,根据用户传入的值初始化selected数据。

具体的数据处理逻辑如何实现可查看源码以及源码注释...

使用Cascader

如下图:

本来想录个操作组件的小视频,无奈我还不知道哪款软件😢 ̄□ ̄||(ps:更多是因为懒😝) 烦请各位大佬下载源码自行体验哈~

源码地址

Cascader

复盘

完成业务需求并不难,可能有很多种的方式可以达到目的。我觉得更多的是在开发过程中的思考以及如何设计是写代码一开始很重要的一部分。

在开发这个组件的时候,和小伙伴有个小插曲碰撞:就是用户选中节点数据合不合理的问题?

怎么理解呢? 我所理解的开发组件是给用户操作的,选中节点数据是否符合业务需求我认为是另外一个层面上业务逻辑问题了,应该另做判断,另外封装规则。(ps:我想大家都有自己的理解和想法吧~)

不足:目前只支持单选功能,组件还可以再开发多选功能(ps:后续有时间迭代,敬请期待...)

团队协作,避免不了你需要在别人的代码基础上去实现新的功能。谈谈在已有的功能基础上去迭代新的需求,日常你是如何实践的呢?

总结

在开发的过程当中,我还是觉得封装组件设计组件是有必要的,为项目管理、后续的迭代添加不一样的色彩。

这篇简文是lin年前最后一篇的总结了,对各大掘友有帮助的话希望赏个 star ~

最后,提前祝大家新年快乐,新的一年万事顺意~