作者: 彭道宽
转发链接:https://mp.weixin.qq.com/s/dtmZv4YcQitbKajjNbW_Pg
前言
?
我除了菜,啥都不是
?
前段时间,组里决定做一个跨项目、跨业务的 UI 组件库,原因是我们部门的产品越来越多,且每个产品设计到多端(如 Web/Mobile/PC/Android 等)而为了快速响应目标,决定做一套统一且可视化的,拥有部门特色的 UI 组件库。
视觉已经给出了所有组件样式、交互效果,而我们前端组内部也经过一轮轮的评审和讨论,最终每个人都分了几个组件进行开发。而我呢,也分到了几个组件,有稍微简单点的,有存在复杂交互及状态的,这篇文章,主要是记录自己「第一次」开发一个公共组件的思考~
?
我负责的组件是 : Skeleton 骨架占位组件 、Card 卡片组件、Button 按钮组件、栅格组件
?
前期准备工作
这属于第一次开发公共组件,之前呢主要都还是在项目里边,抽离一些简单复用逻辑的业务组件,举个例子,对于 Button 组件,与我来说,我之前更多可能考虑的就只是一些常用的状态,比如说之前的Button组件代码是这样的 :
/**
* @class Button
* @extends {React.Component}
* @property {string} text - 按钮文本
* @property {string} size - 按钮大小,small/middle/big
* @property {string} icon - 按钮携带的icon,不需要则为空
* @property {string} color - 类型,可选值为 orange/ghost/white
* @property {object} style - 样式
* @property {string} textSize - 按钮文案文字大小,small/middle/big/super
* @property {boolean} disabled - 可否点击
* @property {string} iconSeat - 按钮icon的位置,left/right
* @property {function} onHandleClick - 点击事件
* @property {boolean} isLock - 是否锁定点击(注:如果需要使用锁,请保证所有操作为同步或者所有的异步行为执行完再return)
*/
这是我结合业务内容,抽离的Button组件,有一丝丝公共组件的样子,但是其实还是远远不够的。于是,此次在开发公共组件之前,我特意的去做了充足的准备。
组件化开发
什么是组件化?这个问题相信大部分前端工程师都知道~ 在跟我小学弟学妹们装 X 的时候,他们问我,什么是组件,我笑而不语,甩出了www.baidu.com网址,告诉他们,自己查...
?
组件化是指解耦复杂系统时将多个功能模块拆分、重组的过程,有多种属性、状态反映其内部特性。
?
简单来说,我们可以把页面当作是变形金刚,由各不同零件组件,比如说 Header零件、Hand零件、Footer零件等...
然后呢,在我们想要制造一个变形金刚时,直接就用这些零件,就能快速 do 出一个产品了~
设计原则
相信大家也不想听我逼逼,直接进入主题,我想要设计一个大家都能普遍使用的组件,我该如何去设计,我上网搜了许多相关的文章,例如 :
在我看了一些文章之后,整理了一些其它人对组件设计的看法(底部会贴出友情链接),首先,我们得拥有一套组件化设计思维,要它有啥用?它能帮我们高效开发哦~
官方文档
这个文档必须详细,不然别人咋看,同时每一个组件,都应尽可能的表达,该组件的由来、使用场景、如何设计、API、传参等
?
感兴趣的可以去看看 ant design 的文档,它除了使用文档,在 github 上还有每个组件的说明文档
?
代码阅览
应该提供一个可以让开发者实时调试代码的地方,使其他这些组件的使用者可以更好地理解各个 props,相信比较流行热门的 UI 库,都有这种骚操作~
使用实例
提供一些如何将其数据导入 UI 的实例代码,使其他开发者可以更快上手与他们的使用情况。
如何设计
- 标准性
- 独立性
- 复用与易用
- 无环依赖原则(ADP)
- 入口处检查参数的有效性,出口处检查返回的正确性
- 稳定抽象原则(SAP)
- ......
上边如何设计我坦白,是从 聊聊组件设计 写过来的,懒得写了~(尊重作者,尊重原创,大家直接去看他的文章哈~)
着手开发
我们组里的组件库,是基于 Ant Design 进行开发,嗯,我一开始以为是项目中已经 npm install antd 了,谁知道当我去看 package.json时,发现并没得,于是我去问了一下负责这个组件库的 C 同学,原来...是要我们去看 Ant Design 的代码, 然后借鉴一波,去除国际化、还有一些不同的差异项,再加入自己部门特色的交互、样式~
奥力给,这啥啊,什么玩意啊,就直接去看源码了 ???
于是,我从我负责的组件里边,挑选了一个最简单的 Card 组件,进行研究了一波,wc,不看不知道,一看吓一跳...原来我还是太菜了...
这个卡片组件,如果没看源码之前,我估计就是这样 :
/** * @class Button * @extends {React.Component} * @property {string} text - 按钮文本 * @property {string} size - 按钮大小,small/middle/big * @property {string} icon - 按钮携带的icon,不需要则为空 * @property {string} color - 类型,可选值为 orange/ghost/white * @property {object} style - 样式 * @property {string} textSize - 按钮文案文字大小,small/middle/big/super * @property {boolean} disabled - 可否点击 * @property {string} iconSeat - 按钮icon的位置,left/right * @property {function} onHandleClick - 点击事件 * @property {boolean} isLock - 是否锁定点击(注:如果需要使用锁,请保证所有操作为同步或者所有的异步行为执行完再return) */
沿着这个思路,一路走下去,发现,如果传 content 肯定不对啊,为啥,如果用户想改这个文案内容的样式呢,简单,给他开放一个 contentStyle就好了嘛~
那如果用户想换行,又该咋办,简单,这个 content<String> 就改成 content<Array> 嘛,判断 typeof content,如果是数组就遍历渲染文案~
那如果用户传ReactNode类型的呢,比如这样
const loadingNode = (
<div>loading</div>
)
<Card content={loadingNode} />
再或者,用户想这样~
const loadingNode = `<p className="loading">我是loading</p>`;
<Card content={loadingNode} />;
用户真实想要的是,你通过 dangerouslySetInnerHTML 进行转义,而不是你直接显示这个 content。
算了不想了,直接去看源码吧 ~
看源码的痛
初次一看,啥啊,这个 config-provider 是个啥玩意?这个 SizeContext 又是个啥,这个 less 文件咋都是用的 @xxx啊,怎么一个文件引入的这么多变量,都是外部的。
?
当我去看了 React 实战:设计模式和最佳实践 这本小册之后,我知道了,这 config-provider、ConfigConsumer 是个啥玩意。,然后在看完一个完整组件之后,才发现,Ant Design B !!!
?
「以我自己开发的 Card 组件来举例~」 , 我们先来共识一下,这个组件的一些样式
属性
然后呢,我们通过引入 import { ConfigConsumer, ConfigConsumerProps } from '../config-provider'进行处理,「别问,问就是还没咋搞懂,总之就是高阶组件的疯狂操作,感兴趣的可以去看看,这个玩意真的有点意思」 config-provider
我们接着对一些 props 进行处理 ~
// 获取前缀,比如像ant-design一样,所有的class都是以 antd- 开头
const prefixCls = getPrefixCls('card', customizePrefixCls);
// 定义头部
let head: React.ReactNode;
// 定义加载时的状态
let loadingBlock: React.ReactNode;
if (title || extra) {
head = (
<div
style={headStyle}
className={`${prefixCls}-head ${headWrapName && headWrapName}`}
>
<div className={`${prefixCls}-head-wrapper`}>
{title && <div className={`${prefixCls}-head-title`}>{title}</div>}
{extra && <div className={`${prefixCls}-extra`}>{extra}</div>}
</div>
</div>
);
}
const body = (
<div className={`${prefixCls}-body`}>{loading ? loadingBlock : children}</div>
);
在我们导出之前,我们通过SizeContext高阶组件进行包装~
<SizeContext.Consumer>
{size => {
// 如果你有自定义的size,以你的为准,没有则以SizeContext中默认的size
const mergedSize = customizeSize || size;
// 处理所有的className
const classString = classNames(prefixCls, className, {
[`${prefixCls}-loading`]: loading,
[`${prefixCls}-shadow`]: isShadow,
[`${prefixCls}-${mergedSize}`]: mergedSize
});
return (
<div className={classString} style={style} onClick={onClick && onClick}>
{head}
{body}
</div>
);
}}
</SizeContext.Consumer>
对于样式,不是直接在 less 文件写一些 color 或者 font-size 的,对于ant-design来说,他们有一个 style 文件,里边存放着许多定义好的变量,甚至于 theme 文件,可以说,到时候如果想自定义主题,只需要将其中的 theme 文件 copy 一份,然后进行修改,就能直接完成自定义主题的需求了~
怎么说呢,其实抽离了一些复杂的需求出去之后,相对的这个 Card 组件就简单了很多,我们再多去看看几个组件,会发现,真香,原来组件还可以这么设计,相对自己之前设计的那些low B组件,这个组件看起来就高大上太多了。
后续
回过头来看,这篇文章有点像随手写的笔记,没得啥干货,不过「主要的目的还是想传递给大家一个思想:就是有时间,可以考虑去看看一些优秀组件的源码」~ 奥力给 !
「 更新一下,这是后记,我再也不敢说我会写前端 Button组件「实践」」,这是我开发Button组件遇到的问题和思考,希望对你们有点用~
目前进度
组内目前的一个进度也在有序进行中,毕竟大家都一致认同这个项目,且 v1 版本相对较为宽松,先出基础版,再深挖细节和优化 ~
推荐JavaScript经典实例学习资料文章
《细说DOM API中append和appendChild的三个不同点》
《NodeX Component - 滴滴集团 Node.js 生态组件体系「实践」》
《浅谈浏览器架构、单线程js、事件循环、消息队列、宏任务和微任务》
《了不起的 Webpack HMR 学习指南(上)「含源码讲解」》
《了不起的 Webpack HMR 学习指南(下)「含源码讲解」》
《图解 Promise 实现原理(二):Promise 链式调用》
《图解 Promise 实现原理(三):Promise 原型方法实现》
《图解 Promise 实现原理(四):Promise 静态方法实现》
《使用Service Worker让你的 Web 应用如虎添翼(上)「干货」》
《使用Service Worker让你的 Web 应用如虎添翼(中)「干货」》
《使用Service Worker让你的 Web 应用如虎添翼(下)「干货」》
《一个轻量级 JavaScript 全文搜索库,轻松实现站内离线搜索》
《细品269个JavaScript小函数,让你少加班熬夜(一)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(二)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(三)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(四)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(五)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(六)「值得收藏」》
《手把手教你7个有趣的JavaScript 项目-上「附源码」》
《手把手教你7个有趣的JavaScript 项目-下「附源码」》
《JavaScript 使用 mediaDevices API 访问摄像头自拍》
《一文彻底搞懂JavaScript 中Object.freeze与Object.seal的用法》
《可视化的 JS:动态图演示 - 事件循环 Event Loop的过程》
《可视化的 js:动态图演示 Promises & Async/Await 的过程》
《Pug 3.0.0正式发布,不再支持 Node.js 6/8》
《通过发布/订阅的设计模式搞懂 Node.js 核心模块 Events》
《「速围」Node.js V14.3.0 发布支持顶级 Await 和 REPL 增强功能》
《JavaScript 已进入第三个时代,未来将何去何从?》
《前端上传前预览文件 image、text、json、video、audio「实践」》
《深入细品 EventLoop 和浏览器渲染、帧动画、空闲回调的关系》
《推荐13个有用的JavaScript数组技巧「值得收藏」》
《36个工作中常用的JavaScript函数片段「值得收藏」》
《一文了解文件上传全过程(1.8w字深度解析)「前端进阶必备」》
《手把手教你如何编写一个前端图片压缩、方向纠正、预览、上传插件》
《JavaScript正则深入以及10个非常有意思的正则实战》
《前端开发规范:命名规范、html规范、css规范、js规范》
《100个原生JavaScript代码片段知识点详细汇总【实践】》
《手把手教你深入巩固JavaScript知识体系【思维导图】》
《一个合格的中级前端工程师需要掌握的 28 个 JavaScript 技巧》
《身份证号码的正则表达式及验证详解(JavaScript,Regex)》
《127个常用的JS代码片段,每段代码花30秒就能看懂-【上】》
《深入浅出讲解JS中this/apply/call/bind巧妙用法【实践】》
《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》
《面试中教你绕过关于 JavaScript 作用域的 5 个坑》
作者: 彭道宽
转发链接:https://mp.weixin.qq.com/s/dtmZv4YcQitbKajjNbW_Pg