组件化是一种思维的表现,这种技能映射到人的本质是,一个人是否有能力把一个复杂的问题拆解、简单化的能力。
一、组件化诞生的历史
我们在讨论如何拆分组件之前,是有必要简单的了解一下组件化诞生的一个历史。
前端娱乐圈有一个独有的生态:框架。每年出现的框架层出不穷,根本学不完。但是总的来说还是可以分成两个阶段。
第一阶段: JQ和PrototypeJS。 该阶段解决了浏览器的兼容性问题以及API的遍历程度
第二阶段: Vue、React、Angular。解决了组件化、解耦、复用等问题
在大陆,主要讨论的是Vue和React。 有些人说Vue是framework,而React是library,前者有更多的约束和更加齐全的工具链。而后者更加的自由。但是真的要投入生产的话,依旧需求认为的给React添加很多的约束,而且Vue也是支持jsx的,所以我一直不太赞同React更加自由这样的说话。
在我看来,它们在实际生产开发过程中,在那一堆工具链中,只是API的不同而已。
它们都为前端提供了很好的组件化。而且近一年来两者都不约而同朝着函数式跟进。它们带来的各种hook,给我们带来了不一致的组件化的写法。
二、为什么业务组件越开发越难维护
人的问题
当然是人的问题. 或许产品的问题,或许整个工作流程的问题,或许上面的问题. 这些我们暂且不提,我作为开发, 首先是要管好自己的代码组织.
再次我们先排除其他外界的因素,比如产品经常改需求. 仅从编码阶段来说.
以我们团队为例,我们团队内部员工2个,8个外包,外包兄弟们的招聘标准是远低于内部的。团队人员每个人的编码能力差距还是很大的。项目都是长期维护的,一个业务模块就会有很多人维护,在上面不断的填尿加屎。
在这里并不是说外包人员的编码能力差,我们组就有一个外包的兄弟编码能力、解决问题的能力相当厉害的,比很多内部的都好很多。这里只是从平局值上面来说。
团队成员的水平参差不齐, 顾及到团队协作, 我们在拆分组件的时候需要更加的简单和清晰.
技术问题
业务逻辑和交互逻辑的纠缠不清
2.1 项目现状
可以看出, 改变它组件内部状态的来源非常的多, 维护或者修改的时候,需要翻阅的文件目录和范围就很广. 自然就很难维护.
举一个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的理想模型
依旧是A B C 三个组件.但是A B C三个组件外边飘的那些箭头不存在了. 所有能够控制它们的内部状态的方式都集中 在了controllers上面.
其中controllers部分的组织形式和vue的composition api宣传图表现一致。
将相似的功能以及用到的变量都封装在一个函数当中。这一切也更加好的迎合了
实际代码如下:
<template><div><B setC={setC} /><div>
</template>
<script setup>import B from 'B.js'import cController from 'cController.js'const { setC } = cController(props)
</script>
// 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组件.
<template><div><C setC={setC} /><div>
</template>
<script setup>
import C from 'C.js'props: { setC: {type: Number,}}
</script>
也就是说,控制C组件内部状态的是通过引入到A组件中的controller来进行通过,中间的B组件不做任何的处理,仅仅作为一个中转站. 操作起来和理论都很简单。但是想要更好的拆分的话,还需要了解三个概念:
- 业务变脸和UI变量
- 受控组件和非受控组件
- 控制反转ioc
下面我通过一个实际的业务场景来描述。
三、举一个实际的例子
3.1 需求背景
- 功能就是典型的笔记软件的功能,右边可以放各种类型的文件,点击就可以在右边渲染出对应的内容.
- 目录树有两个彩蛋,会根据当前文件类型出现不同的操作
- 目录树下面有一个固定的收藏夹,目录树可以在这其中滚动
3.2 开发之前: 前端设计文档
数据流向图
功能还是很清楚的,但是功能其实很多. 我认为我们团队在开发之前是必须要有的. 作为一个前端, 可以没有流程图,但是一定要下面这样的图. 我在别的地方没有见过这样的图,所以自己给这样的图做了一个定义,叫数据流向图.
关于完整的工作流程,之后再写一篇文章进行描述
- 组件的模块
- 组件之间的控制关系
第一点, 还是比较清楚,就是这个需求可以拆成哪几个模块.
第二点, tree组件和content组件是同级组件, tree可以控制content组件内的状态, content组件也可以改变tree内的状态. 再深入一点说,就是tree点击不同的文件类型, content组件部分就会渲染不同的模块; 而当在content组件内对当前阅读的文件进行删除操作的时候,tree作为目录树自然是要刷新最新的目录信息的.
目录结构
通过上面的结构图,可以得到下面这样目录结构.
逻辑控制
数据流向图中的各个组件都放在根目录下index.vue中挂载. 如下入
控制目录树的相关逻辑都放在listTreeController控制器里边, 和右边内容content相关逻辑都放入到renderContentController的方法当中.
随后将controller中公共方法都传进到组件当中. doc-aside是包括search和tree已经other三个模块的中转组件. 不在这个组件中做任何的逻辑处理. 如下图:
举一个例子, 控制按钮的权限. [背景]
- 所有功能点都受控挂载在vuex的store上面的一个变量, 没有权限的话,就直接通过
v-if来隐藏对应的入口
[之前实现]
- 直接找到对应的按钮在
v-if上,通过root.docAuth('createDoc')来判断
[修改之后]
- 创建来一个
authoControllers.js在index.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框架内置的.
平时工作中很多人是这样传的:
<el-dialog :v-model="data.id === XXXX">// code
</el-dialog>
props = {data: {type: Object}
}
通过通过接口拿到的,或者自己组件的数据传进来之后,再进行对v-model的控制. data.id这样的变量就是业务变量, 通过业务变量来直接控制UI的组件的显示和隐藏,就是业务变量和UI变量的混用. 或者说**业务逻辑和交互逻辑的混用. **
混用之后的后果,就是我们进行维护的时候, 需要查看的变量或者说字段就成倍的增加, 交互变量和业务变量交织在一起. 这部分的代码同时承载了业务逻辑和交互逻辑.
DDD领域模型也是可以解决这个问题, 之后我会再开篇幅聊一聊.
所以我们就需要将业务逻辑和交互逻辑给拆开. 如下:
<template><el-dialog :v-model="isShow"><template slot="header">{{ dialogTitle }}</template><template slot="content">// type === 创建表单// type === 移动文件夹目录</template></el-dialog>
</temaplte>
props = {isShow: {type : Boolean,desc: '是否显示弹窗'},type: {type: String,desc: '弹窗的类型'}
}
其中ishow和type 就可以视为UI变量, 它们不关心外界是通过了什么判断, 只关系传进来的是true还是false.
四、持续的优化
不管一开始代码是如何规划的,如何组织的.最重要的还是要持续的去维护. 屎山到了之后, 前面的维护者没有一个人是无辜的. 但是也不需要过早的去维护.什么时候到了维护重构的时机呢?
- 当碰到这里用的代码别的地方也用到的时候
- 这个变量出现在好几个地方,被好几个地方都set的时候, 而自己搞不懂它们set的顺序的时候
- 函数复杂到自己看了半天都看不明白的时候
五、可能的问题
问题一: 中转的组件没有挂载任何逻辑,为什么还存在?
1.为了之后可能的拆分
2.让结构更加的清晰
问题二: 中转的组件要挂载这么多办法, 或许太难看?
1.实在是太多可以使用vue的$attr和 $listeners
2.为了维护对于数据的溯源
五、实践是学习前端的捷径
前端是一门手艺活,只有实践才能够提高技术. 前端的天花板确实相比其他方向的低,但是也不是我这样的普通人说能够触碰就能触碰到的. 就算很多高端大佬嗤之以鼻的业务代码, 写的时候如果不多思考如何写的简洁,怎么写优雅,写十年和写三年也是没有差别的.
业务才能创造价值, 有了价值才能有我们前端工程师生存的空间. 所以为了提升自己的价值, 提升自己的工资. 平时写业务代码的时候,想想这样写会有什么问题, 如何写才能够更加好. 在这个基础上, 才能看明白那些框架存在的意义. 业务是在轮子之上的,如果对业务的代码都不理解, 又怎么能够真正的写好轮子呢?
所以我们在保障业务按时完成的情况下,应该多尝试,多实践.
触碰到的. 就算很多高端大佬嗤之以鼻的业务代码, 写的时候如果不多思考如何写的简洁,怎么写优雅,写十年和写三年也是没有差别的.
业务才能创造价值, 有了价值才能有我们前端工程师生存的空间. 所以为了提升自己的价值, 提升自己的工资. 平时写业务代码的时候,想想这样写会有什么问题, 如何写才能够更加好. 在这个基础上, 才能看明白那些框架存在的意义. 业务是在轮子之上的,如果对业务的代码都不理解, 又怎么能够真正的写好轮子呢?
所以我们在保障业务按时完成的情况下,应该多尝试,多实践.
最后
整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。




有需要的小伙伴,可以点击下方卡片领取,无偿分享
本文探讨了前端组件化的历史、组件维护困难的原因,特别是人的问题和技术问题,并通过一个实际例子展示了如何拆分组件,强调了业务变量与UI变量的区分以及受控与非受控组件的概念。作者提出,持续优化和实践中学习是提升前端技术的关键。
1117

被折叠的 条评论
为什么被折叠?



