From 8f30a14790bee4071075f791c87e95222f22d01b Mon Sep 17 00:00:00 2001 From: Tom Xu Date: Sat, 25 Apr 2020 20:53:21 +0800 Subject: [PATCH] refactor(badge): rewrite with hook (#23488) * refactor(badge): rewrite with hook * Update ScrollNumber.tsx --- components/badge/ScrollNumber.tsx | 130 +++++++++--------------- components/badge/index.tsx | 160 +++++++++++++++--------------- 2 files changed, 125 insertions(+), 165 deletions(-) diff --git a/components/badge/ScrollNumber.tsx b/components/badge/ScrollNumber.tsx index ba1f5ab96a..cae853e521 100644 --- a/components/badge/ScrollNumber.tsx +++ b/components/badge/ScrollNumber.tsx @@ -50,73 +50,50 @@ export interface ScrollNumberState { count?: string | number | null; } -class ScrollNumber extends React.Component { - static defaultProps = { - count: null, - onAnimated() {}, - }; - - static getDerivedStateFromProps(nextProps: ScrollNumberProps, nextState: ScrollNumberState) { - if ('count' in nextProps) { - if (nextState.count === nextProps.count) { - return null; - } - return { - animateStarted: true, - }; - } - return null; - } - - lastCount?: string | number | null; - - private timeout?: number; - - constructor(props: ScrollNumberProps) { - super(props); - this.state = { - animateStarted: true, - count: props.count, - }; +const ScrollNumber: React.FC = props => { + const [animateStarted, setAnimateStarted] = React.useState(true); + const [count, setCount] = React.useState(props.count); + const [prevCount, setPrevCount] = React.useState(props.count); + const [lastCount, setLastCount] = React.useState(props.count); + + if (prevCount !== props.count) { + setAnimateStarted(true); + setPrevCount(props.count); } - componentDidUpdate(_: any, prevState: ScrollNumberState) { - this.lastCount = prevState.count; - const { animateStarted } = this.state; + React.useEffect(() => { + setLastCount(count); + let timeout: number; if (animateStarted) { - this.clearTimeout(); // Let browser has time to reset the scroller before actually // performing the transition. - this.timeout = setTimeout(() => { - // eslint-disable-next-line react/no-did-update-set-state - this.setState( - (__, props) => ({ - animateStarted: false, - count: props.count, - }), - this.onAnimated, - ); + timeout = setTimeout(() => { + setAnimateStarted(false); + setCount(props.count); + if (props.onAnimated) { + props.onAnimated(); + } }); } - } - - componentWillUnmount() { - this.clearTimeout(); - } + return () => { + if (timeout) { + clearTimeout(timeout); + } + }; + }, [animateStarted, count, props.count, props.onAnimated]); - getPositionByNum(num: number, i: number) { - const { count } = this.state; + const getPositionByNum = (num: number, i: number) => { const currentCount = Math.abs(Number(count)); - const lastCount = Math.abs(Number(this.lastCount)); - const currentDigit = Math.abs(getNumberArray(this.state.count)[i] as number); - const lastDigit = Math.abs(getNumberArray(this.lastCount)[i] as number); + const lstCount = Math.abs(Number(lastCount)); + const currentDigit = Math.abs(getNumberArray(count)[i] as number); + const lastDigit = Math.abs(getNumberArray(lstCount)[i] as number); - if (this.state.animateStarted) { + if (animateStarted) { return 10 + num; } // 同方向则在同一侧切换数字 - if (currentCount > lastCount) { + if (currentCount > lstCount) { if (currentDigit >= lastDigit) { return 10 + num; } @@ -126,20 +103,12 @@ class ScrollNumber extends React.Component return 10 + num; } return num; - } - - onAnimated = () => { - const { onAnimated } = this.props; - if (onAnimated) { - onAnimated(); - } }; - renderCurrentNumber(prefixCls: string, num: number | string, i: number) { + const renderCurrentNumber = (prefixCls: string, num: number | string, i: number) => { if (typeof num === 'number') { - const position = this.getPositionByNum(num, i); - const removeTransition = - this.state.animateStarted || getNumberArray(this.lastCount)[i] === undefined; + const position = getPositionByNum(num, i); + const removeTransition = animateStarted || getNumberArray(lastCount)[i] === undefined; return React.createElement( 'span', { @@ -161,19 +130,18 @@ class ScrollNumber extends React.Component {num} ); - } + }; - renderNumberElement(prefixCls: string) { - const { count } = this.state; + const renderNumberElement = (prefixCls: string) => { if (count && Number(count) % 1 === 0) { return getNumberArray(count) - .map((num, i) => this.renderCurrentNumber(prefixCls, num, i)) + .map((num, i) => renderCurrentNumber(prefixCls, num, i)) .reverse(); } return count; - } + }; - renderScrollNumber = ({ getPrefixCls }: ConfigConsumerProps) => { + const renderScrollNumber = ({ getPrefixCls }: ConfigConsumerProps) => { const { prefixCls: customizePrefixCls, className, @@ -181,9 +149,9 @@ class ScrollNumber extends React.Component title, component = 'sup', displayComponent, - } = this.props; + } = props; // fix https://fb.me/react-unknown-prop - const restProps = omit(this.props, [ + const restProps = omit(props, [ 'count', 'onAnimated', 'component', @@ -214,19 +182,15 @@ class ScrollNumber extends React.Component ), }); } - return React.createElement(component as any, newProps, this.renderNumberElement(prefixCls)); + return React.createElement(component as any, newProps, renderNumberElement(prefixCls)); }; - render() { - return {this.renderScrollNumber}; - } + return {renderScrollNumber}; +}; - private clearTimeout(): void { - if (this.timeout) { - clearTimeout(this.timeout); - this.timeout = undefined; - } - } -} +ScrollNumber.defaultProps = { + count: null, + onAnimated() {}, +}; export default ScrollNumber; diff --git a/components/badge/index.tsx b/components/badge/index.tsx index 9aca29b8fb..4077b16208 100644 --- a/components/badge/index.tsx +++ b/components/badge/index.tsx @@ -32,40 +32,47 @@ function isPresetColor(color?: string): boolean { return (PresetColorTypes as any[]).indexOf(color) !== -1; } -export default class Badge extends React.Component { - static defaultProps = { - count: null, - showZero: false, - dot: false, - overflowCount: 99, - }; - - getNumberedDisplayCount() { - const { count, overflowCount } = this.props; +const Badge: React.FC = props => { + const getNumberedDisplayCount = () => { + const { count, overflowCount } = props; const displayCount = (count as number) > (overflowCount as number) ? `${overflowCount}+` : count; return displayCount as string | number | null; - } + }; - getDisplayCount() { - const isDot = this.isDot(); + const hasStatus = (): boolean => { + const { status, color } = props; + return !!status || !!color; + }; + + const isZero = () => { + const numberedDisplayCount = getNumberedDisplayCount(); + return numberedDisplayCount === '0' || numberedDisplayCount === 0; + }; + + const isDot = () => { + const { dot } = props; + return (dot && !isZero()) || hasStatus(); + }; + + const getDisplayCount = () => { // dot mode don't need count - if (isDot) { + if (isDot()) { return ''; } - return this.getNumberedDisplayCount(); - } + return getNumberedDisplayCount(); + }; - getScrollNumberTitle() { - const { title, count } = this.props; + const getScrollNumberTitle = () => { + const { title, count } = props; if (title) { return title; } return typeof count === 'string' || typeof count === 'number' ? count : undefined; - } + }; - getStyleWithOffset() { - const { offset, style } = this.props; + const getStyleWithOffset = () => { + const { offset, style } = props; return offset ? { right: -parseInt(offset[0] as string, 10), @@ -73,79 +80,61 @@ export default class Badge extends React.Component { ...style, } : style; - } + }; - getBadgeClassName(prefixCls: string, direction: string = 'ltr') { - const { className, children } = this.props; + const getBadgeClassName = (prefixCls: string, direction: string = 'ltr') => { + const { className, children } = props; return classNames(className, prefixCls, { - [`${prefixCls}-status`]: this.hasStatus(), + [`${prefixCls}-status`]: hasStatus(), [`${prefixCls}-not-a-wrapper`]: !children, [`${prefixCls}-rtl`]: direction === 'rtl', }) as string; - } - - hasStatus(): boolean { - const { status, color } = this.props; - return !!status || !!color; - } + }; - isZero() { - const numberedDisplayCount = this.getNumberedDisplayCount(); - return numberedDisplayCount === '0' || numberedDisplayCount === 0; - } - - isDot() { - const { dot } = this.props; - const isZero = this.isZero(); - return (dot && !isZero) || this.hasStatus(); - } - - isHidden() { - const { showZero } = this.props; - const displayCount = this.getDisplayCount(); - const isZero = this.isZero(); - const isDot = this.isDot(); + const isHidden = () => { + const { showZero } = props; + const displayCount = getDisplayCount(); const isEmpty = displayCount === null || displayCount === undefined || displayCount === ''; - return (isEmpty || (isZero && !showZero)) && !isDot; - } + return (isEmpty || (isZero() && !showZero)) && !isDot(); + }; - renderStatusText(prefixCls: string) { - const { text } = this.props; - const hidden = this.isHidden(); + const renderStatusText = (prefixCls: string) => { + const { text } = props; + const hidden = isHidden(); return hidden || !text ? null : {text}; - } + }; - renderDisplayComponent() { - const { count } = this.props; + const renderDisplayComponent = () => { + const { count } = props; const customNode = count as React.ReactElement; if (!customNode || typeof customNode !== 'object') { return undefined; } return React.cloneElement(customNode, { style: { - ...this.getStyleWithOffset(), + ...getStyleWithOffset(), ...(customNode.props && customNode.props.style), }, }); - } + }; - renderBadgeNumber(prefixCls: string, scrollNumberPrefixCls: string) { - const { status, count, color } = this.props; + const renderBadgeNumber = (prefixCls: string, scrollNumberPrefixCls: string) => { + const { status, count, color } = props; - const displayCount = this.getDisplayCount(); - const isDot = this.isDot(); - const hidden = this.isHidden(); + const displayCount = getDisplayCount(); + const dot = isDot(); + const hidden = isHidden(); const scrollNumberCls = classNames({ - [`${prefixCls}-dot`]: isDot, - [`${prefixCls}-count`]: !isDot, + [`${prefixCls}-dot`]: dot, + [`${prefixCls}-count`]: !dot, [`${prefixCls}-multiple-words`]: - !isDot && count && count.toString && count.toString().length > 1, + !dot && count && count.toString && count.toString().length > 1, [`${prefixCls}-status-${status}`]: !!status, [`${prefixCls}-status-${color}`]: isPresetColor(color), }); - let statusStyle: React.CSSProperties | undefined = this.getStyleWithOffset(); + let statusStyle: React.CSSProperties | undefined = getStyleWithOffset(); if (color && !isPresetColor(color)) { statusStyle = statusStyle || {}; statusStyle.background = color; @@ -157,15 +146,15 @@ export default class Badge extends React.Component { data-show={!hidden} className={scrollNumberCls} count={displayCount} - displayComponent={this.renderDisplayComponent()} // }> - title={this.getScrollNumberTitle()} + displayComponent={renderDisplayComponent()} // }> + title={getScrollNumberTitle()} style={statusStyle} key="scrollNumber" /> ); - } + }; - renderBadge = ({ getPrefixCls, direction }: ConfigConsumerProps) => { + const renderBadge = ({ getPrefixCls, direction }: ConfigConsumerProps) => { const { prefixCls: customizePrefixCls, scrollNumberPrefixCls: customizeScrollNumberPrefixCls, @@ -174,7 +163,7 @@ export default class Badge extends React.Component { text, color, ...restProps - } = this.props; + } = props; const omitArr = [ 'count', 'showZero', @@ -189,11 +178,11 @@ export default class Badge extends React.Component { const prefixCls = getPrefixCls('badge', customizePrefixCls); const scrollNumberPrefixCls = getPrefixCls('scroll-number', customizeScrollNumberPrefixCls); - const scrollNumber = this.renderBadgeNumber(prefixCls, scrollNumberPrefixCls); - const statusText = this.renderStatusText(prefixCls); + const scrollNumber = renderBadgeNumber(prefixCls, scrollNumberPrefixCls); + const statusText = renderStatusText(prefixCls); const statusCls = classNames({ - [`${prefixCls}-status-dot`]: this.hasStatus(), + [`${prefixCls}-status-dot`]: hasStatus(), [`${prefixCls}-status-${status}`]: !!status, [`${prefixCls}-status-${color}`]: isPresetColor(color), }); @@ -203,13 +192,13 @@ export default class Badge extends React.Component { } // - if (!children && this.hasStatus()) { - const styleWithOffset = this.getStyleWithOffset(); + if (!children && hasStatus()) { + const styleWithOffset = getStyleWithOffset(); const statusTextColor = styleWithOffset && styleWithOffset.color; return ( @@ -221,7 +210,7 @@ export default class Badge extends React.Component { } return ( - + {children} { ); }; - render() { - return {this.renderBadge}; - } -} + return {renderBadge}; +}; + +Badge.defaultProps = { + count: null, + showZero: false, + dot: false, + overflowCount: 99, +}; + +export default Badge;