为了账号安全,请及时绑定邮箱和手机立即绑定

你能使用 typescript 写好一个最简单的 Button 组件吗?

2020.03.23 10:28 7560浏览

先来一个硬广:我在慕课网上线了新课

面对的用户是:想要提高React 水平,写出符合大厂规范代码,了解开源库的发布验证流程的同学,这门课从设计,到渐进式开发,再到单元测试,发布再到 CI/CD 整个流程全覆盖。同时我还为这门课推出了官方的演示网站:http://vikingship.xyz/ ,有所有组件和文档的演示,可以看看。

这里就讲讲组件库实现的第一个组件 Button,大家一看这个组件,应该是觉得太简单啦,这还用学?闭着眼都能写出来,把需求扔过来吧。结果是怎样呢?让我们来试试吧。

第一步:根据原型图完成需求分析

设计师把热乎乎的需求扔到了我的脸上,这是简化了的原型图,去掉了很多需求。

http://img4.sycdn.imooc.com/5e78300400018dd512560942.jpg

确实看起来也很简单,不就是来回变换 class嘛 ,让一个普通的 button 显示的不一样嘛,这有何难?当然要注意的就是link button,和 disabled 两种状态,它们是两个特殊的属性。将需求分析一下,得到的代码逻辑无非就是这样。

http://img1.sycdn.imooc.com/5e78301e00012c7111101240.jpg

第二步:开始初步编码

好,开始代码,特别注意我们在上面备注提到的两点,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;  
    }  
}

看看它们长得怎样?

http://img3.sycdn.imooc.com/5e7830320001366308720578.jpg

看上去不错,让我们来试试它配合 ts 是否方便,联想是否智能,看看下面这个 gif。

https://s1.ax1x.com/2020/03/23/8TNmMn.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 的东风,自动填充出来。

https://s1.ax1x.com/2020/03/23/8Ttj8H.gif

第五步:未完待续

好,那么到目前为止,我们做出了一个看起来还不错的 Button 组件,其实我们能完成的更多,比如说现在测试完全靠肉眼,需要给它单元测试,假如是一款企业级产品,我们需要自动生成文档,还需要打包成合适的模块类型以及发布到 npm 等等工作。现在是不是感觉,这么一个简单的组件也不是那么简单的,如果一个 Button 组件成功的提起了你的兴趣,不妨来看看和我一起完成一个组件库是多么有趣的一个过程把。

点击查看更多内容

本文原创发布于慕课网 ,转载请注明出处,谢谢合作

4人点赞

若觉得本文不错,就分享一下吧!

评论

相关文章推荐

正在加载中
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消