最近更新有點慢,更新慢的緣由最近在看javascript
題外話說完了,這篇主要是針對上一篇對ant of react的代碼解析只是加了註釋反應很難懂,沒有那麼時間去一個一個仔細看。後面ant發佈了新版本在button組件上對動畫效果作了一些處理,大概的邏輯結構沒變。這篇就用思惟導圖來展現下ant of react button組件js代碼的邏輯結構,畫的很差敬請諒解。css
按鈕的代碼邏輯結構的主線其實就是圍繞按鈕對外開放的功能實現的,全部我想來看看ant desgin of ract 按鈕組件對外開放的功能導圖:html
disabled 按鈕失效狀態前端
ghost 幽靈屬性vue
href 點擊跳轉的地址,指定此屬性 button 的行爲和 a 連接一致java
htmlType 設置 button 原生的 type 值,可選值請參考 HTML 標準node
icon 設置按鈕的圖標類型react
loading 設置按鈕載入狀態angularjs
shape 設置按鈕形狀typescript
size 設置按鈕大小
target 至關於 a 連接的 target 屬性,href 存在時生效
type 設置按鈕類型,可選值爲 primary dashed danger(版本 2.7 中增長) 或者不設
onClick 點擊按鈕時的回調
block 將按鈕寬度調整爲其父寬度的選項
其中致使組件html結構不同的是href功能,因此先看href的實現
/** * 組件內容 */ render() { const { type, shape, size, className, children, icon, prefixCls, ghost, loading: _loadingProp, block, ...rest } = this.props; const { loading, hasTwoCNChar } = this.state; // large => lg // small => sm let sizeCls = ''; switch (size) { case 'large': sizeCls = 'lg'; break; case 'small': sizeCls = 'sm'; default: break; } const now = new Date(); const isChristmas = now.getMonth() === 11 && now.getDate() === 25; /** * 拼接className */ const classes = classNames(prefixCls, className, { [`${prefixCls}-${type}`]: type,//對應type功能 [`${prefixCls}-${shape}`]: shape,//對應shape功能 [`${prefixCls}-${sizeCls}`]: sizeCls,//對應size功能 [`${prefixCls}-icon-only`]: !children && icon,//對應icon功能 [`${prefixCls}-loading`]: loading,//對應loading功能 [`${prefixCls}-background-ghost`]: ghost,//對應ghost功能 [`${prefixCls}-two-chinese-chars`]: hasTwoCNChar, [`${prefixCls}-block`]: 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; /** * 判斷是a標籤仍是button標籤,對應href功能 */ if ('href' in rest) { return ( <a {...rest} className={classes} onClick={this.handleClick} title={title} > {iconNode}{kids} </a> ); } else { // React does not recognize the `htmlType` prop on a DOM element. Here we pick it out of `rest`. const { htmlType, ...otherProps } = rest; return ( <Wave> <button {...otherProps} type={htmlType || 'button'} className={classes} onClick={this.handleClick} title={title} > {iconNode}{kids} </button> </Wave> ); } }
上面的那些功能配置屬性是經過父組件經過props傳遞進來的,那組件代碼中要有接收參數已經檢驗參數類型的處理塊:
/** * 類型別名,這個類型的只能是對應的值 */ 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'; /** * 聲明一個接口BaseButtonProps */ 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; } /** * a標籤的參數組合 */ export type AnchorButtonProps = { href: string; target?: string; onClick?: React.MouseEventHandler<HTMLAnchorElement>; } & BaseButtonProps & React.AnchorHTMLAttributes<HTMLAnchorElement>; /** * button標籤的參數組合 */ export type NativeButtonProps = { htmlType?: ButtonHTMLType; onClick?: React.MouseEventHandler<HTMLButtonElement>; } & BaseButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>; /** * 類型別名 */ export type ButtonProps = AnchorButtonProps | NativeButtonProps; /** * button class聲明 */ export default class Button extends React.Component<ButtonProps, any> { static Group: typeof Group; static __ANT_BUTTON = true; /** * 設置props默認值 */ static defaultProps = { prefixCls: 'ant-btn', loading: false, ghost: false, block: false, }; /** * props類型校驗 */ 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, };
這段代碼大概意思是在typescript中聲明接口和自定義類型來校驗參數對象裏面的鍵值對的數據類型,defaultProps設置參數的某些默認值,propTypes在react中經過prop-types來校驗參數的數據量類型和值。
剩下就是單擊事件和組件聲明週期的一些處理事件
/** * 構造函數 */ constructor(props: ButtonProps) { super(props); this.state = { loading: props.loading, hasTwoCNChar: false, }; }
/** * 單擊事件 */ handleClick: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement> = e => { const { loading } = this.state; const { onClick } = this.props; if (!!loading) { return; } if (onClick) { (onClick as React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>)(e); } }
/** * 組件渲染以後調用,只調用一次。 */ componentDidMount() { this.fixTwoCNChar(); } /** * props改變時調用觸發,nextProps.loading賦值到setState的loading * @param nextProps */ 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); } } /** * 判斷botton的內容是否有兩個中文字 */ 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, }); } } /** * 判斷是不是字符串類型 */ function isString(str: any) { return typeof str === 'string'; } /** * 多箇中文間插入空格 * @param {Object} child 組件的子內容 * @param {Boolean} needInserted 是否插入空格 * @returns {ReactElement} */ // Insert one space between two chinese characters automatically. 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; }