前言:
前端面试中,请你设计一个通用的Select组件,要求手撸代码或说一下设计思想?
我只会写经常使用的业务组件,最多再把数据抽象一下,其余的就不知如何下手了。
今天的文章咱们就先了解一下组件的设计原则,懂理论才能更好的实践。
复制代码
一个组件的复杂度,主要来源就是自身的状态;即组件自身须要维护多少个不依赖于外部输入的状态。html
组件开发中,如何将数据和UI解耦,是最重要的工做。前端
组件开发过程当中,时刻谨记、思考是否符合如下的原则,能够帮助你开发一个更完善的通用组件。react
你的组件是否符合只实现一个职责,而且只有一个改变状态的理由?面试
如fetch请求和渲染逻辑,应该分离。由于fetch请求时会形成组件从新渲染,渲染时的样式或数据格式变化,也会引发组件从新渲染。数组
单一职责能够保证组件是最细的粒度,且有利于复用。但太细的粒度有时又会形成组件的碎片化。网络
所以单一职责组件要创建在可复用的基础上,对于不可复用的单一职责组件,咱们仅仅做为独立组件的内部组件便可。数据结构
组件开发要服务于业务,为了更好的复用,又要从业务中抽离。架构
下面代码实现了需求A:实现一个基础的select组件: menu:是select的下拉列表,menu上面的div是select的选择框头部,包含一个值和一个箭头。ide
<div className={dropdownClass}>
<div className={`${baseClassName}-control ${disabledClass}`} onMouseDown={this.handleMouseDown.bind(this)} onTouchEnd={this.handleMouseDown.bind(this)} >
{value}
<span className={`${baseClassName}-arrow`} />
</div>
{menu}
</div>
复制代码
此时又有一个新的需求B,要求将select选择框头部渲染为一个图片。函数
虽然B的交互模式和 A如出一辙,但由于两者在 DOM 结构上的巨大差异,致使咱们没法复用上面的这个 Select 来实现它。 只能去修改源代码、或从新写一个符合需求的组件。
所以组件开发时最好的作法是放弃对DOM的掌控,只提供最基础的DOM、交互逻辑,将DOM的结构转移给开发者。
下面的代码是Antd的组件DropDown,能够看到只有最基础的DOM,提供了多个渲染函数和处理逻辑。
return (
<Trigger {...otherProps} prefixCls={prefixCls} ref="trigger" popupClassName={overlayClassName} popupStyle={overlayStyle} builtinPlacements={placements} action={trigger} showAction={showAction} hideAction={hideAction} popupPlacement={placement} popupAlign={align} popupTransitionName={transitionName} popupAnimation={animation} popupVisible={this.state.visible} afterPopupVisibleChange={this.afterVisibleChange} popup={this.getMenuElement()} onPopupVisibleChange={this.onVisibleChange} getPopupContainer={getPopupContainer} > {children} </Trigger>
);
复制代码
通用性虽好,但会浪费开发者不少精力,所以在抽象业务组件以前,请问本身:
* 存在代码重复吗?若是只使用一次,或者只是某个特定用例,可能嵌入组件中更好。
* 若是它只是几行代码,分隔它反而须要更多的代码,那是否能够直接嵌入组件中?
* 性能会收到影响吗?更改state/props会致使从新渲染,当发生这种状况时,你须要的是 只是从新去渲染通过diff以后获得的相关元素节点。在较大的、关联很紧密的组件中,你可能会发现状态更改会致使在不须要它的许多地方从新呈现,这时应用的性能就可能会开始受到影响。
* 你是否有一个明确的理由?分离代码我想要实现什么?更松散的耦合、能够被复用等,若是回答不了这个问题,那最好先不要从组件中抽离。
* 这些好处是否超过了成本?分离代码须要花费必定的时间和精力,咱们要在业务中去衡量,有所取舍。
复制代码
良好的组件封装应该隐藏内部细节和实现意义,并经过props来控制行为和输出。
减小访问全局变量:由于它们打破了封装,创造了不可预测的行为,而且使测试变得困难。能够将全局变量做为组件的props,而不是直接引用。
具备多个功能的组件,应该转换为多个小组件。
单一责任原则描述了如何将需求拆分为组件,封装描述了如何组织这些组件,组合描述了如何将整个系统粘合在一块儿。
复制代码
非纯组件有显示的反作用,咱们要尽可能隔离非纯代码。
将全局变量做为props传递给组件,而非将其注入到组件的做用域中。
将网络请求和组件渲染分离,只将数据传递给组件,保证组件职责的单一性,也能将非纯代码从组件中隔离。
复制代码
测试不只仅是自动检测错误,更是检测组件的逻辑。
若是一个组件测试不易于测试,很大多是你的组件设计存在问题。
复制代码
开发人员大部分时间都在阅读和理解代码,而不是实际编写代码。
有意义的函数、变量命名,可让代码具备良好的可读性。
复制代码
前端组件的架构实际上是一个是树状图,当咱们设计一个组件时,推荐用UML类图的形式,将组件结构、数据流动状态、处理函数明确标注。先构思组件细节,再写代码,能够避免代码的屡次反复。
咱们想要实现一个Table组件,Table组件包含行数(RowCount)、header、body。
Table的数据源data、行数RowCount,都来自props;Table内部的排序函数,须要的状态sortPerperty、ascending来自state;
Table的操做包括onRowClick,来自props;排序setSortProperty,来自state;
UML类图以下所示,能够直观的了解组件的UI层结构,数据流动和处理函数,写代码时不再怕会重构了^_^
为了让下一个接手的同事更好的理解代码,咱们有时会在核心代码中,添加必要的注释,让代码更清晰。
let params = {
pageNum: pageNum,
pageSize: pageSize,
status: 4, // 参照:ROBOT_STATUS, 0-新导入,1-审核中,2-审核经过,3-审核未经过,4-上架,5-下架
title: search,
queryType: 0 // 0--只查询列表, 1--查询申请状态
};
复制代码
代码如上,阅读代码时,你能快速了解各个字段的含义,但你会额外分心去看注释,思路被打倒,致使中断了整个函数的逻辑分析。
所以,假数据、非技术说明文档、配置代码,建议放在代码外,而不要放在核心代码中,会影响用户体验。
给组件传递props时,建议用更扁平化的props,而不要用嵌套的对象或数组。
<DetailModal {...modalData} visible={showModal} tagType={ROBOT_TYPE} sceneList={sceneList} handleCloseModal={() => this.handleCloseModal()} />
复制代码
1.前端组件设计原则