📢 我除了菜,啥都不是前端
前段时间,组里决定作一个跨项目、跨业务的 UI 组件库,缘由是咱们部门的产品愈来愈多,且每一个产品设计到多端(如 Web/Mobile/PC/Android 等)而为了快速响应目标,决定作一套统一且可视化的,拥有部门特点的 UI 组件库。git
视觉已经给出了全部组件样式、交互效果,而咱们前端组内部也通过一轮轮的评审和讨论,最终每一个人都分了几个组件进行开发。而我呢,也分到了几个组件,有稍微简单点的,有存在复杂交互及状态的,这篇文章,主要是记录本身第一次开发一个公共组件的思考~github
我负责的组件是 : Skeleton 骨架占位组件 、Card 卡片组件、Button 按钮组件、栅格组件npm
这属于第一次开发公共组件,以前呢主要都仍是在项目里边,抽离一些简单复用逻辑的业务组件,举个例子,对于 Button
组件,与我来讲,我以前更多可能考虑的就只是一些经常使用的状态,好比说以前的Button
组件代码是这样的 :json
/** * @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) */
declare interface ButtonProps {
text?: string
size?: string
icon?: string
color?: string
style?: object
textSize?: string
disabled?: boolean
iconSeat?: string
onHandleClick?: () => void
isLock?: boolean
}
复制代码
这是我结合业务内容,抽离的Button
组件,有一丝丝公共组件的样子,可是其实仍是远远不够的。因而,这次在开发公共组件以前,我特地的去作了充足的准备。设计模式
什么是组件化?这个问题相信大部分前端工程师都知道~ 在跟我小学弟学妹们装 X 的时候,他们问我,什么是组件,我笑而不语,甩出了www.baidu.com
网址,告诉他们,本身查...数组
组件化是指解耦复杂系统时将多个功能模块拆分、重组的过程,有多种属性、状态反映其内部特性。前端工程师
简单来讲,咱们能够把页面看成是变形金刚,由各不一样零件组件,好比说 Header零件
、Hand零件
、Footer零件
等...antd
而后呢,在咱们想要制造一个变形金刚时,直接就用这些零件,就能快速 do 出一个产品了~app
相信你们也不想听我逼逼,直接进入主题,我想要设计一个你们都能广泛使用的组件,我该如何去设计,我上网搜了许多相关的文章,例如 :
在我看了一些文章以后,整理了一些其它人对组件设计的见解(底部会贴出友情连接),首先,咱们得拥有一套组件化设计思惟,要它有啥用?它能帮咱们高效开发啊~
这个文档必须详细,否则别人咋看,同时每个组件,都应尽量的表达,该组件的由来、使用场景、如何设计、API、传参等
👉 感兴趣的能够去看看 ant design 的文档,它除了使用文档,在 github 上还有每一个组件的说明文档
应该提供一个可让开发者实时调试代码的地方,使其余这些组件的使用者能够更好地理解各个 props,相信比较流行热门的 UI 库,都有这种骚操做~
提供一些如何将其数据导入 UI 的实例代码,使其余开发者能够更快上手与他们的使用状况。
上边如何设计
我坦白,是从 👉聊聊组件设计 写过来的,懒得写了~(尊重做者,尊重原创,你们直接去看他的文章哈~)
咱们组里的组件库,是基于 Ant Design
进行开发,嗯,我一开始觉得是项目中已经 npm install antd
了,谁知道当我去看 package.json
时,发现并没得,因而我去问了一下负责这个组件库的 C 同窗,原来...是要咱们去看 Ant Design 的代码, 而后借鉴一波,去除国际化、还有一些不一样的差别项,再加入本身部门特点的交互、样式~
奥力给,这啥啊,什么玩意啊,就直接去看源码了 ???
因而,我挑选了一个最简单的 Card 组件,进行研究了一波,wc,不看不知道,一看吓一跳...原来我仍是太菜了...
这个卡片组件,若是没看源码以前,我估计就是这样 :
/** * @class Card * @extends {React.Component} * @property {string} title - 标题 * @property {string} content - 内容 * @property {string} size - 卡片大小 * @property {string} extra - 右上角extra * @property {object} style - 样式 * @property {function} onClick - 点击事件 */
复制代码
沿着这个思路,一路走下去,发现,若是传 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
啊,怎么一个文件引入的这么多变量,都是外部的。
可是当我去看完一个完整组件以后,才发现,Ant Design 🐂 B !!!
👉 若是你感兴趣的戳这里看源码: Ant Design-Card
以我本身开发的 Card
组件来举例~
咱们来看看,我这个 Card 组件所具有的功能 ...
参数 | 说明 | 类型 | 默认值 | 是否必须 | 版本 |
---|---|---|---|---|---|
size | 卡片大小 | string | middle | 是 | - |
style | 卡片样式 | object | - | 否 | - |
loading | 当卡片内容还在加载中时,能够用 loading 展现一个占位 | boolean | true | 否 | - |
isShadow | 卡片是否存在阴影 | boolean | true | 否 | - |
title | 卡片标题 | string|ReactNode | - | 否 | - |
headStyle | 自定义标题区域样式 | object | - | 否 | - |
headWrapName | 自定义标题区域 className | string | ${prefixCls} -card-head |
否 | - |
extra | 卡片右上角的操做区域 | string|ReactNode | - | 否 | - |
onClick | 卡片点击事 | () => void | - | 否 | - |
因此天然而然的,咱们的 Props 就是这些玩意了~
export interface AbstractCardProps {
size?: string;
style?: React.CSSProperties;
loading?: Boolean;
isShadow?: Boolean;
title?: string | React.ReactNode;
headStyle?: React.CSSProperties;
headWrapName?: string;
extra?: string | React.ReactNode;
onClick?: () => void;
}
复制代码
而后呢,咱们经过引入 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组件,这个组件看起来就高大上太多了。🙃
回过头来看,这篇文章有点像随手写的笔记,没得啥干货,不过主要的目的仍是想传递给你们一个思想:就是有时间,能够考虑去看看一些优秀组件的源码 ~ 奥力给 !
组内目前的一个进度也在有序进行中,毕竟你们都一致认同这个项目,且 v1 版本相对较为宽松,先出基础版,再深挖细节和优化 ~