React | 如何制作一个按钮组件

2024-06-16 23:45:36 浏览数 (1)

概要

本文从零开始,使用React TypeScript的方式制作一个按钮组件。

面临的挑战

起个好名

在计算机中,有一个经常遇到但又十分难缠的问题,起名。好的名字可能是灵感闪现,也可能来自借鉴。所以笔者回忆了下大厂的组件库,决定命名为fafa-design

于是在终端中输出如下指令

代码语言:shell复制
npx create-react-app fafa-design --template typescript

代码结构

关于代码结构,各个公司,乃至各个部门都有各自的规范。本次就是用刚刚初始化的项目结构。见如下

代码语言:txt复制
node_modules
public  // 本机临时演示用,后期删除
src // 本机临时演示用,后期删除
.gitignore
package-lock.json
package.json
README.md
tsconfig.json

在根目录src下新建一个component文件夹,用来存放组件,本期是做一个按钮,那么结构大概就长这样:

代码语言:txt复制
component
    - button                            // button 组件
        - Index.tsx                     // 组件实现
        - PropsType.tsx                 // 组件接口
    - index.ts                          // 对外接口
    - styles                            // 统一样式

样式方案

对于Style,React 强调 All in JS。所以直接在标签上去写部分CSS是可以的。

代码语言:jsx复制
<div style={{background: 'skyblue'}} >。。。</div>

当然,也可以抽离出来,单独作为一个对象。

这样做的优点就是:简单,可以加一些内部处理逻辑。缺点就是你的css属性需要做一些调整,比如下划线转为驼峰:

  • background-color -> backgroundColor 这样需要转换一下的。除此之外,还有统一管理,性能问题等。

那CSS-in-JS方案怎么样?

截取自旧版官方文档截取自旧版官方文档

官方对此保持中立。


  • sass和less

这是比较大众的使用方式,大厂的组件库也大都采用此种。

需求分析

单纯的开发人员对需求都比较敏感,能不做就不做。就笔者来说,一时想不出要做什么功能。索性直接按照大厂的文档来做。

TDesign ButtonTDesign Button

基础功能就是

  • 主题
  • 带Icon
  • 多尺寸

开始编码

原形按钮

写一个基础组件,一般依赖于原html,按钮也不例外。值得一提的是,如果你想在TypeScript中“继承”属性,并且添加自定义,你可以这样写:

代码语言:jsx复制
export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
  /** 类型 */
  type?: "primary" | "secondary" | "link";
  /** 尺寸 */
  size?: "small" | "medium" | "large";
...省略
}
// 画面描绘
<button {...restProps}>{children}</button>;

现在就得到了一个基础按钮,但是不太够好看。

给点颜色

在按钮的使用场景中,使用主要,次要,危险等颜色。不同的组件库,所选的这几种主题略有差别。见下面:

Ant Design的色彩选择Ant Design的色彩选择

设计颜色不在行,来到代码层面,该怎样实现呢?

首先做一个基础style,然后根据type值修改background属性

代码语言:jsx复制
  const buttonStyle = {
    backgroundColor: type === 'primary' ? '#007bff' :
                      type === 'secondary' ? '#6c757d' :
                      type === 'success' ? '#28a745' :
                      type === 'danger' ? '#dc3545' :
                      type === 'warning' ? '#ffc107' :
                      type === 'info' ? '#17a2b8' :
                      '#ffffff',
    color: '#ffffff',  // 这里也建议动态调整,比如深色背景配浅色字。
  ...省略
  };

按钮结合图标

图标有两种,一个是静态的,一个是loading。

无需重绘按钮,因为本身就是可以在button内部加入图标与文字,只需要注意对其方式即可。

比如,我这里加了一个TDesign的上传的图标

代码语言:jsx复制
    <button {...restProps}>
      <svg fill="none" viewBox="0 0 24 24" width="1em" height="1em">
        <path
          fill="currentColor"
          d="M4.6 6.28a7.5 7.5 0 0114.8 0 6.5 6.5 0 011.06 12.01l-.9.46-.9-1.78.88-.46a4.5 4.5 0 00-1.23-8.44l-.77-.14-.05-.78a5.5 5.5 0 00-10.98 0l-.05.78-.77.14a4.5 4.5 0 00-1.23 8.44l.89.46-.91 1.78-.9-.46a6.5 6.5 0 011.06-12zm7.4 3.3L17.41 15 16 16.41l-3-3V23h-2v-9.59l-3 3L6.59 15 12 9.59z"
        ></path>
      </svg>
      {children}
    </button>

然后我们就得到一个

带Icon的按钮带Icon的按钮

优化的点?

如果没有其它的处理,当页面上的元素很多时,会明显变卡。尤其是当一个state hook影响很多组件渲染时。这时会想:如果能告诉他哪些不需要渲染就好了。

React官方早就想到了这一点,所以有了useCallbackuseMemo等hook。

这些钩子的第二个参数就是让我们来告诉React,哪些需要真渲染,哪些需要使用缓存。

代码语言:jsx复制
useCallback(() => {
  // doSomeThing
}, []) 

空数组表示只在创建时生成并缓存。useMemo同理,后者常用于组件的缓存

useMemo 和 useCallback 都可以用于缓存函数,二者有何不同? useMemo 用于缓存计算结果,只有当依赖项发生变化时,才会重新计算。它适用于不经常改变且计算成本较高的值。例如,当你需要根据组件的 props 计算一个复杂的对象或数组时,可以使用 useMemo 来避免不必要的重新计算。 useCallback 用于缓存函数,只有当依赖项发生变化时,才会返回一个新的函数。它适用于作为回调函数的函数,特别是当这个函数作为 prop 传递给子组件时。这样可以避免不必要的重新创建函数,减少组件重新渲染的次数。

不过,你需要注意缓存带来的后果。常常有一些莫名其妙的Bug发生于此。


延迟加载:参考Suspense组件

总结

厘清上述基本逻辑后,再去看组件库的源码可能还是一头雾水。因为需求是迭代来的,代码也是。但是,无论如何改变,你还是能找到基础设计的影子,以及design这一词的含义。希望本文对你有帮助。

0 人点赞