概要
本文从零开始,使用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
文件夹,用来存放组件,本期是做一个按钮,那么结构大概就长这样:
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
这是比较大众的使用方式,大厂的组件库也大都采用此种。
需求分析
单纯的开发人员对需求都比较敏感,能不做就不做。就笔者来说,一时想不出要做什么功能。索性直接按照大厂的文档来做。
基础功能就是
- 主题
- 带Icon
- 多尺寸
开始编码
原形按钮
写一个基础组件,一般依赖于原html,按钮也不例外。值得一提的是,如果你想在TypeScript中“继承”属性,并且添加自定义,你可以这样写:
代码语言:jsx复制export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
/** 类型 */
type?: "primary" | "secondary" | "link";
/** 尺寸 */
size?: "small" | "medium" | "large";
...省略
}
// 画面描绘
<button {...restProps}>{children}</button>;
现在就得到了一个基础按钮,但是不太够好看。
给点颜色
在按钮的使用场景中,使用主要,次要,危险等颜色。不同的组件库,所选的这几种主题略有差别。见下面:
设计颜色不在行,来到代码层面,该怎样实现呢?
首先做一个基础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>
然后我们就得到一个
优化的点?
如果没有其它的处理,当页面上的元素很多时,会明显变卡。尤其是当一个state hook影响很多组件渲染时。这时会想:如果能告诉他哪些不需要渲染就好了。
React官方早就想到了这一点,所以有了useCallback
,useMemo
等hook。
这些钩子的第二个参数就是让我们来告诉React,哪些需要真渲染,哪些需要使用缓存。
代码语言:jsx复制useCallback(() => {
// doSomeThing
}, [])
空数组表示只在创建时生成并缓存。useMemo同理,后者常用于组件的缓存
useMemo 和 useCallback 都可以用于缓存函数,二者有何不同? useMemo 用于缓存计算结果,只有当依赖项发生变化时,才会重新计算。它适用于不经常改变且计算成本较高的值。例如,当你需要根据组件的 props 计算一个复杂的对象或数组时,可以使用 useMemo 来避免不必要的重新计算。 useCallback 用于缓存函数,只有当依赖项发生变化时,才会返回一个新的函数。它适用于作为回调函数的函数,特别是当这个函数作为 prop 传递给子组件时。这样可以避免不必要的重新创建函数,减少组件重新渲染的次数。
不过,你需要注意缓存带来的后果。常常有一些莫名其妙的Bug发生于此。
延迟加载:参考Suspense组件
总结
厘清上述基本逻辑后,再去看组件库的源码可能还是一头雾水。因为需求是迭代来的,代码也是。但是,无论如何改变,你还是能找到基础设计的影子,以及design这一词的含义。希望本文对你有帮助。