资料来源
https://zhuanlan.zhihu.com/p/66960057
组件化思想并不是前端独有的,但却是前端技术的延伸 任何软件开发过程,或多或少都有那么一些组件化的需求。
组件化方案下,我们需要具有组件化设计思维,它是一种【整理术】帮助我们高效开发整合
任何一个组件都应该遵守一套标准,可以使得不同区域的开发人员据此标准开发出一套标准统一的组件
描述了组件的细粒度,遵循单一职责原则,保持组件的纯粹性,属性配置等API对外开放,组件内部状态对外封闭,尽可能的少与业务耦合
UI差异,消化在组件内部(注意并不是写一堆if/else),输入输出友好,易用 。追求短小精悍,Single Point Of Truth法则,就是尽量不要重复代码,出自《The Art of Unix Programming》;
避免暴露组件内部实现,避免直接操作DOM,避免使用ref,使用父组件的 state 控制子组件的状态而不是直接通过 ref 操作子组件 。
入口处检查参数的有效性,出口处检查返回的正确性。
设计不当导致环形依赖示意图
组件间耦合度高,集成测试难 一处修改,处处影响,交付周期长 因为组件之间存在循环依赖,变成了“先有鸡还是先有蛋”的问题。
沿着逆向的依赖关系即可寻找到所有受影响的组件,创建一个共同依赖的新组件。
共同依赖
父组件不依赖子组件,删除某个子组件不会造成功能异常
除了数据,避免复杂的对象,尽量只接收原始类型的值
组件最大的不稳定性来自于展现层,一个组件只做一件事,基于功能做好职责划分。
为了让开发者更关注业务逻辑,涌现出了很多优秀的UI组件库 比如antd
,element-ui
,我们只需要调用API便能满足大部分的业务场景,前端角色后置了,开发变得更简单了
一个容器性质的组件,一般当作一个业务子模块的入口,比如一个路由指向的组件 。
容器组件
<template>
<div class="purchase-box">
<!-- 面包屑导航 -->
<bread-crumbs />
<div class="scroll-content">
<!-- 搜索区域 -->
<Search v-show="toggleFilter" :form="form"/>
<!--展开收起区域-->
<Toggle :toggleFilter="toggleFilter"/>
<!-- 列表区域-->
<List :data="listData"/>
</div>
</template>
主要表现为组件是怎样渲染的,就像一个简单的模版渲染过程。
展示型组件
<template>
<div class="purchase-box">
<el-table
:data="data"
:class="{'is-empty': !data || data.length ==0 }"
>
<el-table-column
v-for = "(item, index) in listItemConfig"
:key="item + index"
:prop="item.prop"
:label="item.label"
:width="item.width ? item.width : ''"
:min-width="item.minWidth ? item.minWidth : ''"
:max-width="item.maxWidth ? item.maxWidth : ''">
</el-table-column>
<!-- 操作 -->
<el-table-column label="操作" align="right" width="60">
<template slot-scope="scope">
<slot :data="scope.row" name="listOption"></slot>
</template>
</el-table-column>
<!-- 列表为空 -->
<template slot="empty">
<common-empty />
</template>
</el-table>
</div>
</template>
<script>
export default {
props: {
listItemConfig:{ //列表项配置
type:Array,
default: () => {
return [{
prop:'sku_name',
label:'商品名称',
minWidth:200
},{
prop:'sku_code',
label:'SKU',
minWidth:120
},{
prop:'product_barcode',
label:'条形码',
minWidth:120
}]
}
}}
}
</script>
通常是根据最小业务状态抽象而出,有些业务组件也具有一定的复用性,但大多数是一次性组件
业务组件
可以在一个或多个APP内通用的组件
特点:复用性强,只通过 props、events 和 slots 等组件接口与外部通信
UI组件
<template>
<div class="empty">
<img src="/images/empty.png" alt>
<p>暂无数据</p>
</div>
</template>
不包含UI层的某个功能的逻辑集合
高阶组件可以看做是函数式编程中的组合 可以把高阶组件看做是一个函数,他接收一个组件作为参数,并返回一个功能增强的组件
高阶组件可以抽象组件公共功能的方法而不污染你本身的组件 比如 debounce 与 throttle
组件协同
容器/展示组件对比
引入容器组件的概念只是一种更好的组织方式。
优先考虑展示组件,当你意识到有一些中间组件不使用它继承的props而是转而传递给他们的子级,每次子级组件需要更多数据时,你都需要重新调整这些中间组件,那么,这时候就要考虑引入容器组件
容器组件和展示组件的区别并没有被严格定义,它们的区别不在技术上而是目的性上。
超过三层之后可见组件的数据传递的过程就会变得越复杂
缩减组件依赖可以提高组件的可复用度
封装性不足或自身越界操作,就可能对自身之外造成了侵入,一个组件不应对其它兄弟组件造成直接影响 。
提示
较常见的一种情况是:组件运行时对window对象添加resize监听事件以实现组件响应视窗尺寸变化事件,这种需求的更好替代方案是:组件提供刷新方法,由父组件实现调用。 次优的方案是,当组件destroy前清理恢复
需要考虑需要适用的不同场景,在组件接口设计时进行必要的兼容
接口设计符合规范和大众习惯,尽量让别人用起来简单易上手,易上手是指更符合直觉。
各组件之前以组合的关系互相配合,也是对功能需求的模块化抽象,当需求变化时可以将实现以模块粒度进行调整 。
明确组件划分依据,目前是两种
这是最容易想到的方法,当一个组件渲染了很多元素,就需要尝试分离这些组件的渲染逻辑 以掘金页面为例
例子
大体上看,可以分为Part1,Part2,Part3
<template>
<div id="app">
<div class="panel">
<div class="part1 left">
<!--内容-->
</div>
<div class="part1 right">
<!--内容-->
</div>
<div class="part1 right">
<!--内容-->
</div>
</div>
</template>
<template>
<div id="app">
<part1 />
<part2 />
<part3 />
</div>
</template>
它们有相似的外层,part2和part3更有相似的titlebar,除了业务内容,完全就是一模一样
<template>
<div class="part">
<header>
<span>{{ title }}</span>
</header>
<slot name="content" />
</div>
</template>
将part内可以抽象的数据都做成了props,利用slot去做模版 那么我们在开发相应Part1,Part2时:
<template>
<div id="app">
<part title="亦舒">
<div slot="content">----</div>
</part>
<part title="兴隆臻园户型">
<div slot="content">-----</div>
</part>
</div>
</template>
更具代表性的示例图:
案例
在业务逻辑层处理,首先要明确一点,这些差异并不是组件本身造成的,是你自己的业务逻辑造成的,所以容器组件(父组件)应该为此买单
结合组件本身和业务上下文将差异合理的消除在内部
比如part3中,其他的part只有一个类似更多>>的link,但是它却有多个(一居,二居...)
这里推荐将这种差异体现在组件内部,设计方法也很多:
组件设计初期,就应该拥有不耦合业务的名字,一个通用的或者说未来可能通用的,要有相对合理的命名,比如 Search,List,尽量不要出现与业务耦合过深的业务名词,通用组件与业务无关,只与自身抽象的组件有关。
在设计组件初期,就应该有这种思想,库通常都想让广大开发者用,在设计组件时,可以降低标准到先做到你的整个APP中通用
组件设计规则明明白白写着我们要遵循单一职责原则,这也带来了上文聊过的过度抽象(组件化)的问题,组件抽离的过程就是无限向无状态(展示型)组件无限靠近的过程。