手写一个antd Button 组件

1,482 阅读5分钟

之前用antd组件库的时候,这组件用起来还是挺爽的,还没有感觉一个组件这么难写,结果自己动手写起来的时候才发现一个小小的button里面也是有不少难点,果然轮子还是用起来爽,造起来痛苦。

废话不多说直入主题,我们开始动手写一个像antd那样完善的组件的时候应该做哪些准备呢?

首先像一套成熟的组件库,它们都会为自己的组件库设置一套色彩体系:他由两套色版组成

1. 系统色板 - 基础色版 + 中性色版

2. 产品色版 - 品牌色 + 功能色版

参考elementUI:

除了一套属于自己的品牌的色彩,还有一些组件库基础系统:比如字体系统,边框阴影,可配置开关等,因为后续个人会陆续学习一系列的组件,所以这里做了一下了解,但是咱目前只是仿照antd一个button的组件,自己只需要挑一些自己的喜欢的色彩字体即可,这里咱就不做过纠缠。

现在咱来分析一个button的需求功能:

1. 不同的 Button Type

primary Default Danger LinkButton(a 标签)

2. 不同的 Button Size

normal small large

3. disabled 状态

disabled(button不可点击  a 不可点击)

首先定义button 上述的基本类型:

//定义按钮尺寸export enum ButtonSize {   Large = 'lg',   Small = "sm"}//定义按钮的类型export enum ButtonType {    Primary = "primary",    Default = 'default',    Danger = 'danger',    Link = 'link' }// 按钮传值接口interface BaseButtonProps {    className?: string;    disabled ?: boolean;    size?:ButtonSize;    btnType?:ButtonType;    children: React.ReactNode;    href?:string}

然后讲上述的基本类型传给Button按钮,但是按钮是分为普通按钮和link形式的a标签,所以需要对类型做一个判断返回不同的标签

const Button: React.FC<BaseButtonProps> = (props) =>{    const {        className,        btnType,        disabled,        size,        children,        href,        ...restProps    }=props    // 类名定义 这里应用了classnames库来帮助我们修改库  生成的结果就类似 className="btn btn-lg btn-danger"     // btn ,btn-lg, btn-primary    const classes = classNames('btn',className,{        [`btn-${btnType}`]:  btnType,        [`btn-${size}`]:  size,        'disabled':(btnType===ButtonType.Link)&& disabled    })    // 这里需要判断一下类名    if(btnType === ButtonType.Link && href){        return (            <a             className={classes}            href={href}            {...restProps}            >{children}</a>        )    }else {        return (            <button             className={classes}            disabled={disabled}            >{children}</button>        )    }}

另外我们这里借助了 classnames 这个库来帮助我们更好的简洁的书写,它的写法更好的帮助我们来定义class类民,上面的代码就可以很方便的生成 className="btn btn-lg btn-danger",当然这里我们也可以用三元算符来,一个个的判断类名,但是代码快会看上去非常的冗杂,classnames的更多学习可参考官方文档 github.com/JedWatson/c…

最后我们在给button的几个属性给上默认值并且抛出即可,几个button组件的基本骨架就做好了。

我们在app.js中引用Button <Button disabled>hello</Button>. <Button btnType={ButtonType.Link} href="http://www.baidu.com">baidu link</Button>

我们能发现代码已经生效了,生成相应的disable属性和 a 标签。

接下来我们可以开始给来丰富button的基本样式,这里我们可以分别给这个按钮不同的类名定义不同的样式:

.btn {   position: relative;    display: inline-block;    font-weight: 400;    line-height: 1.5;    color: #212529;    white-space: nowrap;    text-align: center;    vertical-align: middle;    background-image: none;    border: 1px solid transparent;    padding: 0.75rem 0.375rem;    font-size: 1rem;    border-radius: 0.25rem;    box-shadow: inset 0 1px 0 rgb(255 255 255 / 15%), 0 1px 1px rgb(0 0 0 / 8%);    cursor: pointer;    transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;}
.btn-primary {    color: #fff;    background: #0d6efd;    border-color: #0d6efd;}
.btn-primary:hover {    color: #fff;    background: #3385fd;    border-color: #408cfd;}
button:not(:disabled), [type="button"]:not(:disabled), [type="reset"]:not(:disabled), [type="submit"]:not(:disabled) {    cursor: pointer;}

这里就不全部写了,除了这种方式,更为统一的方式是使用sass,并且结合我们之前写的为组件库设置一套色彩体系,我们可以先写上一套默认的scss 代码,然后运用scss的特点,在不同的class名中引用他的基础的色彩体系,例如我们在基础样式的文件中 _variables.scss 写上button大小对应的样式:

//不同大小按钮的 paddingfont size$btn-padding-y-sm: 0.25rem !default;$btn-padding-x-sm: 0.5rem !default;$btn-font-size-sm: $font-size-sm !default;$btn-padding-y-lg: 0.5rem !default;$btn-padding-x-lg: 1rem !default;$btn-font-size-lg: $font-size-lg !default;

然后在对应的button下的scss文件 Button/_style.scss 写上对应的button-size:

.btn-lg {    @include button-size(        $btn-padding-y-lg,        $btn-padding-x-lg,        $btn-font-size-lg,        $btn-border-radius-lg    );}.btn-sm {    @include button-size(        $btn-padding-y-sm,        $btn-padding-x-sm,        $btn-font-size-sm,        $btn-border-radius-sm    );}

获取对应的button-size:

@mixin button-size($padding-x, $padding-y, $font-size, $border-radius) {    padding: $padding-y $padding-x;    font-size: $font-size;    border-radius: $border-radius;}

具体代码scss代码可参考我的个人仓库 github.com/woshidashua…

这样我们就能获取不同属性类名对应的button样式,但是此时因为我们并没有给button的props上定义button 的方法比如click ,同样的 a 标签也没有传入方法,所以此时我们就需要为它们定义相应的方法,但是如果我们一个个将button 和 a 标签上的方法写上去,那将会非常冗杂,所以我们使用 react 提供的 React.AnchorHTMLAttributes 和React.ButtonHTMLAttributes 来定义上面的所有方法,这里我们用ts 来书写需要用到一个联合属性,意思是将我们原来写的属性和他本身存在的属性做一个交集一起传入到 props 中

// button 和  a的方法 如果一个一个写的方式传入porps那太麻烦了,他上面包含的属性太多type NativeButtonProps =BaseButtonProps & React.ButtonHTMLAttributes<HTMLElement> // 联合类型 buttontype AnchorButtonProps = BaseButtonProps & React.AnchorHTMLAttributes<HTMLElement>// a标签export type ButtonProps = Partial<NativeButtonProps & AnchorButtonProps>  //所有属性可选

完整的button 代码

const Button: React.FC<ButtonProps> = (props) =>{    const {        className,        btnType,        disabled,        size,        children,        href,        ...restProps    }=props    // 类名定义 这里应用了classnames库来帮助我们修改库  生成的结果就类似 className="btn btn-lg btn-danger"     // btn ,btn-lg, btn-primary    const classes = classNames('btn',className,{        [`btn-${btnType}`]:  btnType,        [`btn-${size}`]:  size,        'disabled':(btnType===ButtonType.Link)&& disabled    })    // 这里需要判断一下类名    if(btnType === ButtonType.Link && href){        return (            <a             className={classes}            href={href}            {...restProps}            >{children}</a>        )    }else {        return (            <button             className={classes}            disabled={disabled}            >{children}</button>        )    }}Button.defaultProps={    disabled:false,    btnType:ButtonType.Default}

最后咱再来看看效果吧!

最后再贴上仓库地址:

github.com/woshidashua…

参考资料:

juejin.cn/post/684490…

juejin.cn/post/692259…

coding.imooc.com/class/chapt…

jeryqwq.github.io/Others/Antd…

juejin.cn/post/697404…

ps: 求点赞求收藏求转发,点赞过 5 个,下一个组件 menu 一周内更新