前端渣渣开发UI公共组件的新认识

📢 我除了菜,啥都不是前端

前段时间,组里决定作一个跨项目、跨业务的 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 的实例代码,使其余开发者能够更快上手与他们的使用状况。

如何设计

  1. 标准性
  2. 独立性
  3. 复用与易用
  4. 无环依赖原则(ADP)
  5. 入口处检查参数的有效性,出口处检查返回的正确性
  6. 稳定抽象原则(SAP)
  7. ......

上边如何设计我坦白,是从 👉聊聊组件设计 写过来的,懒得写了~(尊重做者,尊重原创,你们直接去看他的文章哈~)

着手开发

咱们组里的组件库,是基于 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 版本相对较为宽松,先出基础版,再深挖细节和优化 ~

相关连接

相关文章
相关标签/搜索