經過官方API文章,你們知道<Button /> 組件具有如下幾個功能點
一、多種樣式風格可選: primary、ghost、 danger等,而且每一種風格都對應各自風格的交互
二、接收click事件回調函數
三、能夠指定點擊跳轉指定的url
四、能夠控制圖標旋轉,模擬請求狀態pendingjavascript
源碼以下css
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> ) } }
調用方式以下html
improt {SimpleBtn} from 'smpePath'; class Clent extends React.Component{ render(){ return ( <div> // 顯示一個紅色的按鈕 <SimpleBtn type="danger"></SimpleBtn> // 顯示一個藍色按鈕 <SimpleBtn type="primary"></SimpleBtn> </div> ) } }
相信看到這裏,你們對這種設計模式已經瞭然於心了。在後面的組件中會大量出現這種組件設計模式。
本篇完前端