1、需求分析
一个Button分以下几个不同的维度
- 不同的Button Type
- Primary
- Default
- Danger
- LinkButton
- 等等
- 不同的Button Size
- Normal
- Small
- Large
- 状态
定义props类型
interface BaseButtonProps {
className?: string;
/**设置 Button 的禁用 */
disabled?: boolean;
/**设置 Button 的尺寸 */
size?: ButtonSize;
/**设置 Button 的类型 */
btnType?: ButtonType;
children: React.ReactNode;
href?: string;
}
4、逻辑编写
我们需要注意以下几点:
我们来写第一版代码:
import React, { FC } from "react";
import classNames from "classnames";
export type ButtonSize = "lg" | "sm";
export type ButtonType = "primary" | "default" | "danger" | "link";
interface BaseButtonProps {
/**设置 Button 的禁用 */
disabled?: boolean;
/**设置 Button 的尺寸 */
size?: ButtonSize;
/**设置 Button 的类型 */
btnType?: ButtonType;
children: React.ReactNode;
href?: string;
}
export const Button: FC<BaseButtonProps> = (props) => {
const { btnType, disabled, size, children, href } = props;
const classes = classNames("btn", {
[`btn-${btnType}`]: btnType,
[`btn-${size}`]: size,
disabled: btnType === "link" && disabled,
});
if (btnType === "link" && href) {
return (
<a className={classes} href={href}>
{children}
</a>
);
} else {
return (
<button className={classes} disabled={disabled} >
{children}
</button>
);
}
};
Button.defaultProps = {
disabled: false,
btnType: "default",
};
export default Button;
在第一版本代码可以看出,我们的Button中缺少原生组件所带的属性,我们来分别定义一下:
// button
type NativeButtonProps = BaseButtonProps & ButtonHTMLAttributes<HTMLElement>;
// a
type AnchorButtonProps = BaseButtonProps & AnchorHTMLAttributes<HTMLElement>;
还要思考我们不会传递所有的属性,所以我们把属性设置为可选:
export type ButtonProps = Partial<NativeButtonProps & AnchorButtonProps>;
此外我们除了会传递BaseButtonProps上的参数,还会传递自己定义的className和原生标签上的其他属性。
我们来写第二版代码:
import React, { FC, ButtonHTMLAttributes, AnchorHTMLAttributes } from "react";
import classNames from "classnames";
export type ButtonSize = "lg" | "sm";
export type ButtonType = "primary" | "default" | "danger" | "link";
interface BaseButtonProps {
className?: string;
/**设置 Button 的禁用 */
disabled?: boolean;
/**设置 Button 的尺寸 */
size?: ButtonSize;
/**设置 Button 的类型 */
btnType?: ButtonType;
children: React.ReactNode;
href?: string;
}
type NativeButtonProps = BaseButtonProps & ButtonHTMLAttributes<HTMLElement>;
type AnchorButtonProps = BaseButtonProps & AnchorHTMLAttributes<HTMLElement>;
export type ButtonProps = Partial<NativeButtonProps & AnchorButtonProps>;
export const Button: FC<ButtonProps> = (props) => {
const { btnType, className, disabled, size, children, href, ...restProps } =
props;
// btn, btn-lg, btn-primary
const classes = classNames("btn", className, {
[`btn-${btnType}`]: btnType,
[`btn-${size}`]: size,
disabled: btnType === "link" && disabled,
});
if (btnType === "link" && href) {
return (
<a className={classes} href={href} {...restProps}>
{children}
</a>
);
} else {
return (
<button className={classes} disabled={disabled} {...restProps}>
{children}
</button>
);
}
};
Button.defaultProps = {
disabled: false,
btnType: "default",
};
export default Button;
这样我们就完成了一个Button组件的全部逻辑。接下来我们解读一下样式编写;
5、样式编写
样式编写需要注意以下几点:
- 变量采用的是全局定义方式
- 重复代码采用mixin
代码如下:
.btn {
position: relative;
display: inline-block;
font-weight: $btn-font-weight;
line-height: $btn-line-height;
color: $body-color;
white-space: nowrap;
text-align: center;
vertical-align: middle;
background-image: none;
border: $btn-border-width solid transparent;
@include button-size( $btn-padding-y, $btn-padding-x, $btn-font-size, $border-radius);
box-shadow: $btn-box-shadow;
cursor: pointer;
transition: $btn-transition;
&.disabled,
&[disabled] {
cursor: not-allowed;
opacity: $btn-disabled-opacity;
box-shadow: none;
> * {
pointer-events: none;
}
}
}
.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);
}
.btn-primary {
@include button-style($primary, $primary, $white)
}
.btn-danger {
@include button-style($danger, $danger, $white)
}
.btn-default {
@include button-style($white, $gray-400, $body-color, $white, $primary, $primary)
}
.btn-link {
font-weight: $font-weight-normal;
color: $btn-link-color;
text-decoration: $link-decoration;
box-shadow: none;
&:hover {
color: $btn-link-hover-color;
text-decoration: $link-hover-decoration;
}
&:focus,
&.focus {
text-decoration: $link-hover-decoration;
box-shadow: none;
}
&:disabled,
&.disabled {
color: $btn-link-disabled-color;
pointer-events: none;
}
}