1、需求分析

一个Button分以下几个不同的维度

  1. 不同的Button Type
    • Primary
    • Default
    • Danger
    • LinkButton
    • 等等
  2. 不同的Button Size
    • Normal
    • Small
    • Large
  3. 状态
    • Disabled

      2、定义项目目录

      image.png

      3、类型定义

      我们使用字面量类型来定义ButtonSize和ButtonType
      1. export type ButtonSize = "lg" | "sm";
      2. export type ButtonType = "primary" | "default" | "danger" | "link";

定义props类型

  1. interface BaseButtonProps {
  2. className?: string;
  3. /**设置 Button 的禁用 */
  4. disabled?: boolean;
  5. /**设置 Button 的尺寸 */
  6. size?: ButtonSize;
  7. /**设置 Button 的类型 */
  8. btnType?: ButtonType;
  9. children: React.ReactNode;
  10. href?: string;
  11. }

4、逻辑编写

我们需要注意以下几点:

我们来写第一版代码:

  1. import React, { FC } from "react";
  2. import classNames from "classnames";
  3. export type ButtonSize = "lg" | "sm";
  4. export type ButtonType = "primary" | "default" | "danger" | "link";
  5. interface BaseButtonProps {
  6. /**设置 Button 的禁用 */
  7. disabled?: boolean;
  8. /**设置 Button 的尺寸 */
  9. size?: ButtonSize;
  10. /**设置 Button 的类型 */
  11. btnType?: ButtonType;
  12. children: React.ReactNode;
  13. href?: string;
  14. }
  15. export const Button: FC<BaseButtonProps> = (props) => {
  16. const { btnType, disabled, size, children, href } = props;
  17. const classes = classNames("btn", {
  18. [`btn-${btnType}`]: btnType,
  19. [`btn-${size}`]: size,
  20. disabled: btnType === "link" && disabled,
  21. });
  22. if (btnType === "link" && href) {
  23. return (
  24. <a className={classes} href={href}>
  25. {children}
  26. </a>
  27. );
  28. } else {
  29. return (
  30. <button className={classes} disabled={disabled} >
  31. {children}
  32. </button>
  33. );
  34. }
  35. };
  36. Button.defaultProps = {
  37. disabled: false,
  38. btnType: "default",
  39. };
  40. export default Button;

在第一版本代码可以看出,我们的Button中缺少原生组件所带的属性,我们来分别定义一下:

  1. // button
  2. type NativeButtonProps = BaseButtonProps & ButtonHTMLAttributes<HTMLElement>;
  3. // a
  4. type AnchorButtonProps = BaseButtonProps & AnchorHTMLAttributes<HTMLElement>;

还要思考我们不会传递所有的属性,所以我们把属性设置为可选:

  1. export type ButtonProps = Partial<NativeButtonProps & AnchorButtonProps>;

此外我们除了会传递BaseButtonProps上的参数,还会传递自己定义的className和原生标签上的其他属性。
我们来写第二版代码:

  1. import React, { FC, ButtonHTMLAttributes, AnchorHTMLAttributes } from "react";
  2. import classNames from "classnames";
  3. export type ButtonSize = "lg" | "sm";
  4. export type ButtonType = "primary" | "default" | "danger" | "link";
  5. interface BaseButtonProps {
  6. className?: string;
  7. /**设置 Button 的禁用 */
  8. disabled?: boolean;
  9. /**设置 Button 的尺寸 */
  10. size?: ButtonSize;
  11. /**设置 Button 的类型 */
  12. btnType?: ButtonType;
  13. children: React.ReactNode;
  14. href?: string;
  15. }
  16. type NativeButtonProps = BaseButtonProps & ButtonHTMLAttributes<HTMLElement>;
  17. type AnchorButtonProps = BaseButtonProps & AnchorHTMLAttributes<HTMLElement>;
  18. export type ButtonProps = Partial<NativeButtonProps & AnchorButtonProps>;
  19. export const Button: FC<ButtonProps> = (props) => {
  20. const { btnType, className, disabled, size, children, href, ...restProps } =
  21. props;
  22. // btn, btn-lg, btn-primary
  23. const classes = classNames("btn", className, {
  24. [`btn-${btnType}`]: btnType,
  25. [`btn-${size}`]: size,
  26. disabled: btnType === "link" && disabled,
  27. });
  28. if (btnType === "link" && href) {
  29. return (
  30. <a className={classes} href={href} {...restProps}>
  31. {children}
  32. </a>
  33. );
  34. } else {
  35. return (
  36. <button className={classes} disabled={disabled} {...restProps}>
  37. {children}
  38. </button>
  39. );
  40. }
  41. };
  42. Button.defaultProps = {
  43. disabled: false,
  44. btnType: "default",
  45. };
  46. export default Button;

这样我们就完成了一个Button组件的全部逻辑。接下来我们解读一下样式编写;

5、样式编写

样式编写需要注意以下几点:

  • 变量采用的是全局定义方式
  • 重复代码采用mixin

代码如下:

  1. .btn {
  2. position: relative;
  3. display: inline-block;
  4. font-weight: $btn-font-weight;
  5. line-height: $btn-line-height;
  6. color: $body-color;
  7. white-space: nowrap;
  8. text-align: center;
  9. vertical-align: middle;
  10. background-image: none;
  11. border: $btn-border-width solid transparent;
  12. @include button-size( $btn-padding-y, $btn-padding-x, $btn-font-size, $border-radius);
  13. box-shadow: $btn-box-shadow;
  14. cursor: pointer;
  15. transition: $btn-transition;
  16. &.disabled,
  17. &[disabled] {
  18. cursor: not-allowed;
  19. opacity: $btn-disabled-opacity;
  20. box-shadow: none;
  21. > * {
  22. pointer-events: none;
  23. }
  24. }
  25. }
  26. .btn-lg {
  27. @include button-size($btn-padding-y-lg, $btn-padding-x-lg, $btn-font-size-lg, $btn-border-radius-lg);
  28. }
  29. .btn-sm {
  30. @include button-size($btn-padding-y-sm, $btn-padding-x-sm, $btn-font-size-sm, $btn-border-radius-sm);
  31. }
  32. .btn-primary {
  33. @include button-style($primary, $primary, $white)
  34. }
  35. .btn-danger {
  36. @include button-style($danger, $danger, $white)
  37. }
  38. .btn-default {
  39. @include button-style($white, $gray-400, $body-color, $white, $primary, $primary)
  40. }
  41. .btn-link {
  42. font-weight: $font-weight-normal;
  43. color: $btn-link-color;
  44. text-decoration: $link-decoration;
  45. box-shadow: none;
  46. &:hover {
  47. color: $btn-link-hover-color;
  48. text-decoration: $link-hover-decoration;
  49. }
  50. &:focus,
  51. &.focus {
  52. text-decoration: $link-hover-decoration;
  53. box-shadow: none;
  54. }
  55. &:disabled,
  56. &.disabled {
  57. color: $btn-link-disabled-color;
  58. pointer-events: none;
  59. }
  60. }

6、本节代码

https://github.com/linhexs/component-library/tree/1.Button