React:实现一个带有loading效果的按钮组件

蚊子前端博客
发布于 2019-05-10 21:16
在react中如何实现一个带有loading效果的按钮组件呢?

在业务经常需要点击按钮去请求接口,在接口还未返回结果前,需要给用户一个等待的感觉,同时锁住按钮,防止产生二次点击!

我们实现带有loading效果的按钮组件,主要是实现以下的几个功能:

  • 有菊花或者圈圈的loading图标,且loading颜色与字体颜色相同;

  • loading的过程中,点击无效;

1. 按钮的结构

针对第1个功能,我最开始是把接口返回结果之前的loading集成到了按钮中,这个loading是用多个div拼接而成的,而loading的颜色则是渲染div中的after伪元素的背景色:

COPYJSX

/** * .lds-spinner div:after { content: ''; display: block; position: absolute; top: 3px; left: 29px; width: 5px; height: 14px; border-radius: 20%; background: #f3434a; } */ render() { return ( <div className="i-loading"> <div className="lds-spinner"> { Array(12).fill(0).map((item, index) => <div key={index}></div>) } </div> </div> ); }

这种方式如果要loading效果跟按钮字体保持一个颜色,则需要通过props传入一个色值,然后在Button组件中才能修改。实在是不太方便,我既要在CSS中设置按钮的颜色,还要通过props传给Button组件,当按钮的字体颜色需要更新时,则需要修改两个地方。再一个,loading图案的大小也要跟着按钮的大小进行变化,而div整体不太好调整,能想到的方法是使用transform: scale来对loading图案放大或者缩小。

这时,loading效果用SVG实现最好了,配上currentColor,能天然继承其父级元素的color值。再使用em单位适应按钮的大小:

COPYJSX

render() { return ( <div> <i className="i-icon-loading"> <svg viewBox="0 0 1024 1024" className="i-icon-loading-spin" data-icon="loading" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false"> <path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 0 0-94.3-139.9 437.71 437.71 0 0 0-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"></path> </svg> </i> <span>立即领取</span> </div> ); }

2. loading效果

按钮最少要接收2个参数,一个是表示是否正在loading中,再一个是接收click事件:

COPYJSX

/** .i-button { .i-icon-loading { margin-right: 8px; } } .i-button-loading { position: relative; &:before{ content: ''; position: absolute; background-color: #ffffff; opacity: 0.4; top: -1px; right: -1px; bottom: -1px; left: -1px; z-index: 1; transition: opacity .2s; border-radius: inherit; } } */ interface ButtonProps { loading: boolean; onClick: React.MouseEventHandler; } export default class Button extends React.Component<ButtonProps> { handleClick = (e: any) => { const { loading, onClick } = this.props; if (!!loading) { // 正在loading中时,直接返回 return; } if (onClick) { (onClick as React.MouseEventHandler)(e); } } render() { const { loading } = this.props; // 将loading效果提取出来 const iconNode = loading ? <IconLoading /> : null; // loading时添加loading的class const loddingClass = loading ? ' i-button-loading' : ''; return ( <div onClick={this.handleClick} className={"i-button" + loddingClass }> {iconNode} <span>{this.props.children}</span> </div> ); } }

loading的icon组件如下:

COPYJSX

/** .i-icon-loading { font-size: 36px; transition: transform .3s ease-in-out; transition: transform .3s ease-in-out; will-change: transform; display: inline-block; color: inherit; font-style: normal; line-height: 0; text-align: center; text-transform: none; vertical-align: -0.125em; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; .i-icon-loading-spin { display: inline-block; animation: loadingCircle 1s infinite linear; } @-webkit-keyframes loadingCircle { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg) } } @keyframes loadingCircle { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg) } } } * */ export default class IconLoading extends Component { render() { return ( <i className="i-icon-loading"> <svg viewBox="0 0 1024 1024" className="i-icon-loading-spin" data-icon="loading" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false"> <path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 0 0-94.3-139.9 437.71 437.71 0 0 0-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"></path> </svg> </i> ); } }

3. 总结

第一次开始使用React写项目,之前都是用Vue在写项目。用Vue写了几个项目了,想着换一种技术栈来写项目,React跟Vue有一些相似之处,不过还是有些不同的,感觉React中很多地方需要自己完善,比如react的路由守卫beforeEach,CSS没有scoped属性,老担心不同的组件之前会产生样式冲突,虽然可以使用*.module.css来规避,不过这样在jsx中写className就不太方便了!另一方面来讲,写React特别能锻炼自己的js能力,比如高阶组件、ES6等新特性的使用,同时react和typescript结合的特别好。以后,随着对React更深入的了解,当然,这些也就不是问题了!

参考:

https://www.zhangxinxu.com/wordpress/2014/07/svg-sprites-fill-color-currentcolor/

标签:
阅读(5985)

公众号:

qrcode

微信公众号:前端小茶馆