vue中拆分组件的实战案例

组件化是一种思维的表现,这种技能映射到人的本质是,一个人是否有能力把一个复杂的问题拆解、简单化的能力。

一、组件化诞生的历史

我们在讨论如何拆分组件之前,是有必要简单的了解一下组件化诞生的一个历史。

前端娱乐圈有一个独有的生态:框架。每年出现的框架层出不穷,根本学不完。但是总的来说还是可以分成两个阶段。

第一阶段: JQ和PrototypeJS。 该阶段解决了浏览器的兼容性问题以及API的遍历程度

第二阶段: Vue、React、Angular。解决了组件化、解耦、复用等问题

在大陆,主要讨论的是Vue和React。 有些人说Vue是framework,而React是library,前者有更多的约束和更加齐全的工具链。而后者更加的自由。但是真的要投入生产的话,依旧需求认为的给React添加很多的约束,而且Vue也是支持jsx的,所以我一直不太赞同React更加自由这样的说话。

在我看来,它们在实际生产开发过程中,在那一堆工具链中,只是API的不同而已

它们都为前端提供了很好的组件化。而且近一年来两者都不约而同朝着函数式跟进。它们带来的各种hook,给我们带来了不一致的组件化的写法。

二、为什么业务组件越开发越难维护

人的问题

当然是人的问题. 或许产品的问题,或许整个工作流程的问题,或许上面的问题. 这些我们暂且不提,我作为开发, 首先是要管好自己的代码组织.

再次我们先排除其他外界的因素,比如产品经常改需求. 仅从编码阶段来说.

以我们团队为例,我们团队内部员工2个,8个外包,外包兄弟们的招聘标准是远低于内部的。团队人员每个人的编码能力差距还是很大的。项目都是长期维护的,一个业务模块就会有很多人维护,在上面不断的填尿加屎。

在这里并不是说外包人员的编码能力差,我们组就有一个外包的兄弟编码能力、解决问题的能力相当厉害的,比很多内部的都好很多。这里只是从平局值上面来说。

团队成员的水平参差不齐, 顾及到团队协作, 我们在拆分组件的时候需要更加的简单和清晰.

技术问题

业务逻辑和交互逻辑的纠缠不清

2.1 项目现状

vue中拆分组件的实战案例_第1张图片

以该图为例, A B C 分别是父子孙组件. 当我们要控制其中一个组件的状态的是, 可以通过很多方式来进行控制. 这些方式的来源有可能是全局变量vuex时间总线来自自己父组件或子组件的改变等等.

可以看出, 改变它组件内部状态的来源非常的多, 维护或者修改的时候,需要翻阅的文件目录和范围就很广. 自然就很难维护.

举一个mixins的例子:

假设它混入了这么多功能。

export default {
  mixins: [ a, b, c, d, e, f, g],
  mounted() {
    console.log(this.whoAreYou)
  }
}

这个this.whoAreYou你能够知道来源于哪一个么? 而如果改成hook的写法来引入某个JS中的变量:

const { IamI } = myHome()

const { IamI as me } = myHome()

这就很简洁干净。在你维护代码的时候,可以很好的进行溯源。 而上面的一切,导致难以维护的原因总结来说有两个:

  • 混用业务变量和UI变量
  • 不区分受控组件和非受控组件

下面我会实际例子分别介绍这个两个概念。而基于hooks的复用才是我们现在解决组件化复用的更好的选择。

2.2 理想目标

基于hook的理想模型

vue中拆分组件的实战案例_第2张图片

依旧是A B C 三个组件.但是A B C三个组件外边飘的那些箭头不存在了. 所有能够控制它们的内部状态的方式都集中 在了controllers上面.

其中controllers部分的组织形式和vue的composition api宣传图表现一致。

vue中拆分组件的实战案例_第3张图片

将相似的功能以及用到的变量都封装在一个函数当中。这一切也更加好的迎合了

实际代码如下:




// cController.js
export default c(props) {
  const c = ref('')
  const setC() {
    c.value = 'I an cController'
  }

  return {
    c,
    setC,
  }
}

cController.js就是controllers中的一个void. 引入到A组件当中,然后将里面的方法通过props传给B组件.


也就是说,控制C组件内部状态的是通过引入到A组件中的controller来进行通过,中间的B组件不做任何的处理,仅仅作为一个中转站. 操作起来和理论都很简单。但是想要更好的拆分的话,还需要了解三个概念:

  • 业务变脸和UI变量
  • 受控组件和非受控组件
  • 控制反转ioc

下面我通过一个实际的业务场景来描述。

三、举一个实际的例子

3.1 需求背景

vue中拆分组件的实战案例_第4张图片

vue中拆分组件的实战案例_第5张图片

简单的截两张图. 需求大致如下:

  • 功能就是典型的笔记软件的功能,右边可以放各种类型的文件,点击就可以在右边渲染出对应的内容.
  • 目录树有两个彩蛋,会根据当前文件类型出现不同的操作
  • 目录树下面有一个固定的收藏夹,目录树可以在这其中滚动

3.2 开发之前: 前端设计文档

数据流向图

功能还是很清楚的,但是功能其实很多. 我认为我们团队在开发之前是必须要有的. 作为一个前端, 可以没有流程图,但是一定要下面这样的图. 我在别的地方没有见过这样的图,所以自己给这样的图做了一个定义,叫数据流向图.

关于完整的工作流程,之后再写一篇文章进行描述

vue中拆分组件的实战案例_第6张图片

它是有两部分构成:

  • 组件的模块
  • 组件之间的控制关系

第一点, 还是比较清楚,就是这个需求可以拆成哪几个模块.

第二点, tree组件和content组件是同级组件, tree可以控制content组件内的状态, content组件也可以改变tree内的状态. 再深入一点说,就是tree点击不同的文件类型, content组件部分就会渲染不同的模块; 而当在content组件内对当前阅读的文件进行删除操作的时候,tree作为目录树自然是要刷新最新的目录信息的.

目录结构

通过上面的结构图,可以得到下面这样目录结构.

vue中拆分组件的实战案例_第7张图片

逻辑控制

数据流向图中的各个组件都放在根目录下index.vue中挂载. 如下入

vue中拆分组件的实战案例_第8张图片

控制目录树的相关逻辑都放在listTreeController控制器里边, 和右边内容content相关逻辑都放入到renderContentController的方法当中.

vue中拆分组件的实战案例_第9张图片

随后将controller中公共方法都传进到组件当中. doc-aside是包括searchtree已经other三个模块的中转组件. 不在这个组件中做任何的逻辑处理. 如下图:

vue中拆分组件的实战案例_第10张图片

举一个例子, 控制按钮的权限. [背景]

  • 所有功能点都受控挂载在vuex的store上面的一个变量, 没有权限的话,就直接通过v-if来隐藏对应的入口

[之前实现]

  • 直接找到对应的按钮在v-if上,通过root.docAuth('createDoc')来判断

[修改之后]

  • 创建来一个authoControllers.jsindex.vue引入, 需要用的地方是应用的是

[具体实现]

export default function authController({ root }) {
  const menuAuth = {
    [MENTY_TYPE.rename]: root.docAuth('rename'),
    [MENTY_TYPE.delete]: root.docAuth('delete')
    // ....
  }
}

虽然在Index.vue中引入,不管是通过props,还是通过依赖注入来给子组件来使用,都不重要.重要的是,它统一管理, 并在index.vue中引入是唯一一个入口. 当我们维护的时候, 只需要通过子组件一路找到对应的controller就可以找到对应的逻辑了.

拆分的原则

  • 对于组件的拆分一开始不需要太细
  • 拆分好受控组件和非受控组件

3.3 受控组件和非受控组件

我们使用的任何UI框架都是受控组件, 受控组件的概念就是它里面的状态都是受调用它的组件来控制的. 非受控组件反之.

3.4 开发进行: 逻辑变量和UI变量

UI变量其实很好理解. 像element-ui的组件中所需要的属性就是UI变量. 但是对于我们实际业务当中, 会对这些进行一定扩展.

举一个例子, 在上面的目录中dialog组件的显示或隐藏,是通过model-value / v-model来进行控制的, true就显示, false就隐藏起来.

隐藏和显示的渐入渐出效果是elementUI框架内置的.

平时工作中很多人是这样传的:


  // code

props = {
  data: {
    type: Object
  }
}

通过通过接口拿到的,或者自己组件的数据传进来之后,再进行对v-model的控制. data.id这样的变量就是业务变量, 通过业务变量来直接控制UI的组件的显示和隐藏,就是业务变量和UI变量的混用. 或者说**业务逻辑和交互逻辑的混用. **

混用之后的后果,就是我们进行维护的时候, 需要查看的变量或者说字段就成倍的增加, 交互变量和业务变量交织在一起. 这部分的代码同时承载了业务逻辑和交互逻辑.

DDD领域模型也是可以解决这个问题, 之后我会再开篇幅聊一聊.

所以我们就需要将业务逻辑和交互逻辑给拆开. 如下: