你能使用 typescript 写好一个最简单的 Button 组件吗?
先来一个硬广:我在慕课网上线了新课
面对的用户是:想要提高React 水平,写出符合大厂规范代码,了解开源库的发布验证流程的同学,这门课从设计,到渐进式开发,再到单元测试,发布再到 CI/CD 整个流程全覆盖。同时我还为这门课推出了官方的演示网站:http://vikingship.xyz/ ,有所有组件和文档的演示,可以看看。
这里就讲讲组件库实现的第一个组件 Button,大家一看这个组件,应该是觉得太简单啦,这还用学?闭着眼都能写出来,把需求扔过来吧。结果是怎样呢?让我们来试试吧。
第一步:根据原型图完成需求分析
设计师把热乎乎的需求扔到了我的脸上,这是简化了的原型图,去掉了很多需求。
确实看起来也很简单,不就是来回变换 class嘛 ,让一个普通的 button 显示的不一样嘛,这有何难?当然要注意的就是link button,和 disabled 两种状态,它们是两个特殊的属性。将需求分析一下,得到的代码逻辑无非就是这样。
第二步:开始初步编码
好,开始代码,特别注意我们在上面备注提到的两点,typescript 有字符串字面量这种美好的东西,自然适合一系列定死的常量。
// 把要的依赖整出来
import React, { FC } from ‘react’
// 大小两种 size
export type ButtonSize = ‘lg’ | ‘sm’
// 四种不同的 type
export type ButtonType = ‘primary’ | ‘default’ | ‘danger’ | ‘link’
//属性很重要 确定好它就很完美了
interface BaseButtonProps {
className?: string;
/**设置 Button 的禁用 _/
disabled?: boolean;
/**设置 Button 的尺寸_ /
size?: ButtonSize;
/**设置 Button 的类型 _/
btnType?: ButtonType;
href?: string;
}
接下来就可以写组件的主体啦
export const Button: FC<BaseButtonProps> = (props) => {
//取出属性
const {
btnType,
className,
disabled,
size,
children,
href,
} = props
//根据流程图 来渲染不同的内容
if (btnType = ‘link’ && href ) {
return (
<a href={href}>
{children}
</a>
)
} else {
return (
<button>
{children}
</button>
)
}}
接下来就是工作的大头 拼接 class 了,这里让找出我们的 添加 class 好帮手 https://github.com/JedWatson/classnames#readme 大家自己研究文档,用法我就不累赘了。
// btn, btn-lg, btn-primary,
// 添加根据type 和 size 两类特殊的 class 名称
const classes = classNames(‘btn’, className, {
[`btn-${btnType}`]: btnType,
[`btn-${size}`]: size,
})
别忘了我们说过的特殊 disabled 属性,button 天然支持这个属性,而 a 链接没有这么一个属性,所以我们需要用样式来模拟这个属性,所以我们给 a 链接添加一个特殊的 class,然后把它们填充到 button 和 a 元素上。
const classes = classNames(‘btn’, className, {
…
// link 用来模拟 disabled 的样式和行为欧!
‘disabled’: (btnType = ‘link’) && disabled
})
…
<a
className={classes}
href={href}
>
{children}
</a>
…
<button
className={classes}
// 我是原生属性!
disabled={disabled}
>
{children}
</button>
第三步:弄点好看的样式
结构到这里没啥问题,接下来就来添加样式,我们使用 scss,样式我不会多写,但是我们要学会两点:1 将一些数值定义成变量,假如我们要做一个组件库,那么用户自定义也是非常重要的,通过这种方法,可以让用户覆盖你的默认样式。2 使用 mixin 来梳理可以重复的逻辑,比如这里的大小和不同的 type 明显就是两个不同的 mixin。
.btn {
… 各种样式
font-weight: $btn-font-weight;
line-height: $btn-line-height;
color: $body-color;
box-shadow: none;
@include button-size( $btn-padding-y, $btn-padding-x, $btn-font-size, $border-radius);
&.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)
}
//对于 link ,比较特殊,给它单独拿出来
.btn-link {
// … 省略掉各种样式
// 特殊处理 disabled
&:disabled,
&.disabled {
color: $btn-link-disabled-color;
pointer-events: none;
}
}
看看它们长得怎样?
看上去不错,让我们来试试它配合 ts 是否方便,联想是否智能,看看下面这个 gif。
初次感受还不错,那么这个简单的组件是否完美了?大家都知道 button 组件和 a 链接都是标准的 HTML 元素,它们都有很多原生的属性,https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/button,button 有比如 name,type,a链接有 target,rel 等等。现在我们在组件中输入这些属性根本没用!自然就没有 ts 的自动补全,这是一个大问题。
第四步:继续编码 - 进化组件
大家都知道这些原生属性有一大堆,你看了 mdn 的文档就知道,难道我们要一个一个写出来嘛?那样太麻烦了,可以直接放弃了。这时候 React 里面预定义的属性和 ts 的内置类型来帮我们了,我们来看看怎么做把!
// react 内置了 button 和 anchor 的类型,里面有它们的全部属性,你可以按住 cmd点击一下试试
import React, { FC, ButtonHTMLAttributes, AnchorHTMLAttributes } from ‘react’
//下面我们要做的就是扩充已经创建好的 ButtonProps,我们使用 ts 的交叉类型来完成它
type NativeButtonProps = BaseButtonProps & ButtonHTMLAttributes<HTMLElement>
type AnchorButtonProps = BaseButtonProps & AnchorHTMLAttributes<HTMLElement>
// 最后,让我们把它们合体,重建,然后用ts 内置的 Partial 把这些属性都转换成可选
export type ButtonProps = Partial<NativeButtonProps & AnchorButtonProp>
这样 ButtonProps 就拥有了这些各种各样的原生属性。现在让我们把这些属性取出来并且放置在组件上,
const {
btnType,
className,
disabled,
size,
children,
href,
// 使用最好用的 三个点 spread 把剩下的属性都取出来
…restProps
} = props
//然后再照猫画虎用 spread 赋值在组件上
return (
<a
className={classes}
href={href}
{…restProps}
>
{children}
</a>
)
return (
<button
className={classes}
disabled={disabled}
{…restProps}
>
{children}
</button>
)
好,最后来试试这些原生属性是否会借 ts 的东风,自动填充出来。
第五步:未完待续
好,那么到目前为止,我们做出了一个看起来还不错的 Button 组件,其实我们能完成的更多,比如说现在测试完全靠肉眼,需要给它单元测试,假如是一款企业级产品,我们需要自动生成文档,还需要打包成合适的模块类型以及发布到 npm 等等工作。现在是不是感觉,这么一个简单的组件也不是那么简单的,如果一个 Button 组件成功的提起了你的兴趣,不妨来看看和我一起完成一个组件库是多么有趣的一个过程把。
共同学习,写下你的评论
评论加载中...
作者其他优质文章