内容简介:源码如下调用方式如下相信看到这里,大家对这种设计模式已经了然于心了。在后面的组件中会大量出现这种组件设计模式。
年底正式总结的好时机, Button组件的源码。
-
Button分析
通过 官方API 文章,大家知道<Button /> 组件具备以下几个功能点
1、多种样式风格可选: primary、ghost、 danger等,并且每一种风格都对应各自风格的交互
2、接收click事件回调函数
3、可以指定点击跳转指定的url
4、可以控制图标旋转,模拟请求状态pending
源码如下
import * as React from 'react'; import { findDOMNode } from 'react-dom'; import * as PropTypes from 'prop-types'; import classNames from 'classnames'; /* 引入了一个系的模块Wave,可能是功能函数,可能是组件,先不管它是什么,用到时再回来看 */ import Wave from '../_util/wave'; import Icon from '../icon'; import Group from './button-group'; //* 组件逻辑的一些辅助常量 */ const rxTwoCNChar = /^[\u4e00-\u9fa5]{2}$/; /* 判断是否为两个中文字符*/ const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar); function isString(str: any) { return typeof str === 'string'; } // 组件逻辑函数: 在两个中文字符间插入一个空格 function insertSpace(child: React.ReactChild, needInserted: boolean) { // Check the child if is undefined or null. if (child == null) { return; } const SPACE = needInserted ? ' ' : ''; // strictNullChecks oops. if (typeof child !== 'string' && typeof child !== 'number' && isString(child.type) && isTwoCNChar(child.props.children)) { return React.cloneElement(child, {}, child.props.children.split('').join(SPACE)); } if (typeof child === 'string') { if (isTwoCNChar(child)) { child = child.split('').join(SPACE); } return <span>{child}</span>; } return child; } /* 联合类型 Button.props中 type、shape、size、htmlType的取值范围 */ export type ButtonType = 'default' | 'primary' | 'ghost' | 'dashed' | 'danger'; export type ButtonShape = 'circle' | 'circle-outline'; export type ButtonSize = 'small' | 'default' | 'large'; export type ButtonHTMLType = 'submit' | 'button' | 'reset'; /* 定义接口 相当于props-types */ export interface BaseButtonProps { type?: ButtonType; icon?: string; shape?: ButtonShape; size?: ButtonSize; loading?: boolean | { delay?: number }; prefixCls?: string; className?: string; ghost?: boolean; block?: boolean; children?: React.ReactNode; } export type AnchorButtonProps = { href: string; target?: string; onClick?: React.MouseEventHandler<HTMLAnchorElement>; } & BaseButtonProps & React.AnchorHTMLAttributes<HTMLAnchorElement>; export type NativeButtonProps = { htmlType?: ButtonHTMLType; onClick?: React.MouseEventHandler<HTMLButtonElement>; } & BaseButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>; export type ButtonProps = AnchorButtonProps | NativeButtonProps; export default class Button extends React.Component<ButtonProps, any> { static Group: typeof Group; static __ANT_BUTTON = true; static defaultProps = { prefixCls: 'ant-btn', loading: false, ghost: false, block: false, }; static propTypes = { type: PropTypes.string, shape: PropTypes.oneOf(['circle', 'circle-outline']), size: PropTypes.oneOf(['large', 'default', 'small']), htmlType: PropTypes.oneOf(['submit', 'button', 'reset']), onClick: PropTypes.func, loading: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]), className: PropTypes.string, icon: PropTypes.string, block: PropTypes.bool, }; private delayTimeout: number; constructor(props: ButtonProps) { super(props); this.state = { /** 控制Button中Icon来旋转,通常应用在异步请返回之前的场景中,比如提交表单,请求结束前让Icon旋转,可以使得体验更好,用来实现文章开头时所描述的功能4 */ loading: props.loading, /** 作为子元素中是否有两个中文字符的标识符, 以此作为是否插入空格的标识符*/ hasTwoCNChar: false, }; } componentDidMount() { this.fixTwoCNChar(); } componentWillReceiveProps(nextProps: ButtonProps) { const currentLoading = this.props.loading; const loading = nextProps.loading; if (currentLoading) { clearTimeout(this.delayTimeout); } if (typeof loading !== 'boolean' && loading && loading.delay) { this.delayTimeout = window.setTimeout(() => this.setState({ loading }), loading.delay); } else { this.setState({ loading }); } } componentDidUpdate() { this.fixTwoCNChar(); } componentWillUnmount() { if (this.delayTimeout) { clearTimeout(this.delayTimeout); } } fixTwoCNChar() { // Fix for HOC usage like <FormatMessage /> const node = (findDOMNode(this) as HTMLElement); const buttonText = node.textContent || node.innerText; if (this.isNeedInserted() && isTwoCNChar(buttonText)) { if (!this.state.hasTwoCNChar) { this.setState({ hasTwoCNChar: true, }); } } else if (this.state.hasTwoCNChar) { this.setState({ hasTwoCNChar: false, }); } } handleClick: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement> = e => { const { onClick } = this.props; if (onClick) { (onClick as React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>)(e); } } isNeedInserted() { const { icon, children } = this.props; return React.Children.count(children) === 1 && !icon; } render() { /** 通过Props来生成不同的className而达到不同的效果,有兴趣的可以研究下样式 */ const { type, shape, size, className, children, icon, prefixCls, ghost, loading: _loadingProp, block, ...rest } = this.props; const { loading, hasTwoCNChar } = this.state; /* 通过size控制按钮的大小尺寸*/ let sizeCls = ''; switch (size) { case 'large': sizeCls = 'lg'; break; case 'small': sizeCls = 'sm'; default: break; } /* Antd 圣诞彩蛋事件代码,每年12.25搞一次, UI变换思路依旧是通过props.someKey 配合不同的className来实现 */ /* 修复方式 https://github.com/ant-design/ant-design/issues/13848*/ const now = new Date(); const isChristmas = now.getMonth() === 11 && now.getDate() === 25; /** 通过Props来生成不同的className而达到不同的UI效果吗,有兴趣的可以研究下样式 */ const classes = classNames(prefixCls, className, { [`${prefixCls}-${type}`]: type, [`${prefixCls}-${shape}`]: shape, [`${prefixCls}-${sizeCls}`]: sizeCls, [`${prefixCls}-icon-only`]: !children && icon, [`${prefixCls}-loading`]: loading, [`${prefixCls}-background-ghost`]: ghost, [`${prefixCls}-two-chinese-chars`]: hasTwoCNChar, [`${prefixCls}-block`]: block, christmas: isChristmas, }); const iconType = loading ? 'loading' : icon; const iconNode = iconType ? <Icon type={iconType} /> : null; const kids = (children || children === 0) ? React.Children.map(children, child => insertSpace(child, this.isNeedInserted())) : null; const title= isChristmas ? 'Ho Ho Ho!' : rest.title; /* 可以指定按钮跳转地址,实现功能3 */ if ('href' in rest) { return ( <a {...rest} className={classes} onClick={this.handleClick} title={title} > {iconNode}{kids} </a> ); } else { // 这里的注释的意思是React不推荐在DOM element上使用 ‘htmlType' 这个属性,因此在前面的IProps接口中,没有定义htmlType,但仍可以使用 // 在ES6与React,通常使用(剩余参数)这种方式可以扩展组件的IProps接口 // React does not recognize the `htmlType` prop on a DOM element. Here we pick it out of `rest`. const { htmlType, ...otherProps } = rest; /** * 这里出现了开头的的外部依赖Wave,看到这种写法,对React组件设计比较熟悉的应该能猜到这个Wave是做什么的了,没错Wave是容器组件 * React推崇的是组件化开发,经过这些年的发展与沉淀,有两个关键词越来越活跃:`compose`与`recompose`,看过 设计模式 的知道,良好的软件设计因该是组合优于继承的,这两个关键词也是这个思路 * 为了提高组件的可复用性,社区同时提出了几种React组件的设计思路HOC、Render Callback、 容器组件与展示组件等 * 组件拆分的目的是为了复用,复用什么呢? 通常是是UI逻辑 * 这里我们先不去关注关注这个Wave是做什么的,我们只需要知道此时,返回一个<button><button>即可, 我们在下一篇文章中去看下这个Wave组件,这里我 * 这里我们只需要知道返回了一个button DOM元素,可以接收className、click事件、可以指定点击时跳转到指定url, * */ return ( <Wave> <button {...otherProps} type={htmlType || 'button'} className={classes} onClick={this.handleClick} title={title} > {iconNode}{kids} </button> </Wave> ); } } }
分析过`<Button />`组件,再结合之前的`<Icon />`组件,我们其实可以发现一些Antd的一点设计模式(经过两年的React项目踩坑,回过头来看时,发现React社区中存在着大量的设计模式),将之成为`Control CSS with Props`,后面简称为`CCP`。 在以前JQuery + CSS 横扫各大浏览器的时候,大家写CSS时已经注意到了复用的便利性,下面的代码,前端开发人员肯定写过,我们来看下面这段css代码
// 抽取出一个组件的样式 .btn { display: inline-block; font-weight: @btn-font-weight; text-align: center; touch-action: manipulation; cursor: pointer; background-image: none; border: @border-width-base @border-style-base transparent; white-space: nowrap; .button-size(@btn-height-base; @btn-padding-base; @font-size-base; @btn-border-radius-base); user-select: none; transition: all .3s @ease-in-out; position: relative; box-shadow: 0 2px 0 rgba(0, 0, 0, .015); } // 在此基础上变形,与扩展 .btn-danger{ color: red; } .btn-primary{ background: blue; }
相信上面这段代码对前端开人员来说,如果放到`html + css`中,如喝水吃饭一样习以为常, 不过是之前的模式在React中经过了变化,此模式将在后面的代码中大量出现,所以与大家约定这种`CCP`的名字 css已经有了,怎么跟Html匹配上呢,看下JSX中的写法
class SimpleBtn extends React.component { render(){ const {type} = this.this.props; const cls = 'btn'; /** 根据props.type 来生成不同DOM节点 */ const btnCls = classNames({ [`${cls}-danger`]: type === 'danger', [`${cls}-primary`]: type === 'type', }, cls); return ( <button className={btnCls}> {this.props.children} </button> ) } }
调用方式如下
improt {SimpleBtn} from 'smpePath'; class Clent extends React.Component{ render(){ return ( <div> // 显示一个红色的按钮 <SimpleBtn type="danger"></SimpleBtn> // 显示一个蓝色按钮 <SimpleBtn type="primary"></SimpleBtn> </div> ) } }
相信看到这里,大家对这种设计模式已经了然于心了。在后面的组件中会大量出现这种组件设计模式。
本篇完
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- React Hooks 源码解析(一):类组件、函数组件、纯组件
- 权限组件的源码分析
- OpenFalcon 源码分析(Nodata组件)
- 【React源码解读】- 组件的实现
- 深入剖析Vue源码 - 组件基础
- 深入剖析Vue源码 - 组件进阶
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。