refactor(button): rewrite with hook (#23367)

This commit is contained in:
Tom Xu 2020-04-18 13:06:04 +08:00 committed by GitHub
parent f4c489553f
commit 09e69c385e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 146 additions and 188 deletions

View File

@ -263,6 +263,7 @@ describe('Button', () => {
throw new Error('Should not called!!!'); throw new Error('Should not called!!!');
}, },
}); });
wrapper.find('Button').instance().forceUpdate();
expect(wrapper.find('Button').instance()).toBe(null);
}); });
}); });

View File

@ -4,7 +4,7 @@ import classNames from 'classnames';
import omit from 'omit.js'; import omit from 'omit.js';
import Group from './button-group'; import Group from './button-group';
import { ConfigContext, ConfigConsumerProps } from '../config-provider'; import { ConfigContext } from '../config-provider';
import Wave from '../_util/wave'; import Wave from '../_util/wave';
import { Omit, tuple } from '../_util/type'; import { Omit, tuple } from '../_util/type';
import warning from '../_util/warning'; import warning from '../_util/warning';
@ -104,72 +104,57 @@ export type NativeButtonProps = {
export type ButtonProps = Partial<AnchorButtonProps & NativeButtonProps>; export type ButtonProps = Partial<AnchorButtonProps & NativeButtonProps>;
interface ButtonState { interface ButtonTypeProps extends React.FC<ButtonProps> {
loading?: boolean | { delay?: number }; Group: typeof Group;
hasTwoCNChar: boolean; __ANT_BUTTON: boolean;
} }
class Button extends React.Component<ButtonProps, ButtonState> { const Button: ButtonTypeProps = ({ ...props }) => {
static Group: typeof Group; const [loading, setLoading] = React.useState(props.loading);
const [hasTwoCNChar, setHasTwoCNChar] = React.useState(false);
const { getPrefixCls, autoInsertSpaceInButton, direction } = React.useContext(ConfigContext);
const buttonRef = React.createRef<HTMLButtonElement>();
let delayTimeout: number;
static __ANT_BUTTON = true; const isNeedInserted = () => {
const { icon, children, type } = props;
static contextType = ConfigContext; return React.Children.count(children) === 1 && !icon && type !== 'link';
static defaultProps = {
loading: false,
ghost: false,
block: false,
htmlType: 'button' as ButtonProps['htmlType'],
}; };
private delayTimeout: number; const fixTwoCNChar = () => {
// Fix for HOC usage like <FormatMessage />
private buttonNode: HTMLElement | null; if (!buttonRef || !buttonRef.current || autoInsertSpaceInButton === false) {
return;
constructor(props: ButtonProps) {
super(props);
this.state = {
loading: props.loading,
hasTwoCNChar: false,
};
} }
const buttonText = buttonRef.current.textContent;
componentDidMount() { if (isNeedInserted() && isTwoCNChar(buttonText)) {
this.fixTwoCNChar(); if (!hasTwoCNChar) {
setHasTwoCNChar(true);
} }
} else if (hasTwoCNChar) {
componentDidUpdate(prevProps: ButtonProps) { setHasTwoCNChar(false);
this.fixTwoCNChar();
if (prevProps.loading && typeof prevProps.loading !== 'boolean') {
clearTimeout(this.delayTimeout);
} }
const { loading } = this.props;
if (loading && typeof loading !== 'boolean' && loading.delay) {
this.delayTimeout = window.setTimeout(() => {
this.setState({ loading });
}, loading.delay);
} else if (prevProps.loading !== loading) {
// eslint-disable-next-line react/no-did-update-set-state
this.setState({ loading });
}
}
componentWillUnmount() {
if (this.delayTimeout) {
clearTimeout(this.delayTimeout);
}
}
saveButtonRef = (node: HTMLElement | null) => {
this.buttonNode = node;
}; };
handleClick: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement> = e => { React.useEffect(() => {
const { loading } = this.state; if (props.loading && typeof props.loading !== 'boolean') {
const { onClick } = this.props; clearTimeout(delayTimeout);
}
if (props.loading && typeof props.loading !== 'boolean' && props.loading.delay) {
delayTimeout = window.setTimeout(() => {
setLoading(props.loading);
}, props.loading.delay);
} else if (props.loading !== loading) {
setLoading(props.loading);
}
}, [props.loading]);
React.useEffect(() => {
fixTwoCNChar();
}, [buttonRef]);
const handleClick = (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement, MouseEvent>) => {
const { onClick } = props;
if (loading) { if (loading) {
return; return;
} }
@ -178,35 +163,6 @@ class Button extends React.Component<ButtonProps, ButtonState> {
} }
}; };
fixTwoCNChar() {
const { autoInsertSpaceInButton }: ConfigConsumerProps = this.context;
// Fix for HOC usage like <FormatMessage />
if (!this.buttonNode || autoInsertSpaceInButton === false) {
return;
}
const buttonText = this.buttonNode.textContent;
if (this.isNeedInserted() && isTwoCNChar(buttonText)) {
if (!this.state.hasTwoCNChar) {
this.setState({
hasTwoCNChar: true,
});
}
} else if (this.state.hasTwoCNChar) {
this.setState({
hasTwoCNChar: false,
});
}
}
isNeedInserted() {
const { icon, children, type } = this.props;
return React.Children.count(children) === 1 && !icon && type !== 'link';
}
render() {
const { getPrefixCls, autoInsertSpaceInButton, direction }: ConfigConsumerProps = this.context;
return ( return (
<SizeContext.Consumer> <SizeContext.Consumer>
{size => { {size => {
@ -222,8 +178,7 @@ class Button extends React.Component<ButtonProps, ButtonState> {
ghost, ghost,
block, block,
...rest ...rest
} = this.props; } = props;
const { loading, hasTwoCNChar } = this.state;
warning( warning(
!(typeof icon === 'string' && icon.length > 2), !(typeof icon === 'string' && icon.length > 2),
@ -272,18 +227,13 @@ class Button extends React.Component<ButtonProps, ButtonState> {
const kids = const kids =
children || children === 0 children || children === 0
? spaceChildren(children, this.isNeedInserted() && autoInsertSpace) ? spaceChildren(children, isNeedInserted() && autoInsertSpace)
: null; : null;
const linkButtonRestProps = omit(rest as AnchorButtonProps, ['htmlType', 'loading']); const linkButtonRestProps = omit(rest as AnchorButtonProps, ['htmlType', 'loading']);
if (linkButtonRestProps.href !== undefined) { if (linkButtonRestProps.href !== undefined) {
return ( return (
<a <a {...linkButtonRestProps} className={classes} onClick={handleClick} ref={buttonRef}>
{...linkButtonRestProps}
className={classes}
onClick={this.handleClick}
ref={this.saveButtonRef}
>
{iconNode} {iconNode}
{kids} {kids}
</a> </a>
@ -298,8 +248,8 @@ class Button extends React.Component<ButtonProps, ButtonState> {
{...(omit(otherProps, ['loading']) as NativeButtonProps)} {...(omit(otherProps, ['loading']) as NativeButtonProps)}
type={htmlType} type={htmlType}
className={classes} className={classes}
onClick={this.handleClick} onClick={handleClick}
ref={this.saveButtonRef} ref={buttonRef}
> >
{iconNode} {iconNode}
{kids} {kids}
@ -314,7 +264,16 @@ class Button extends React.Component<ButtonProps, ButtonState> {
}} }}
</SizeContext.Consumer> </SizeContext.Consumer>
); );
} };
}
Button.defaultProps = {
loading: false,
ghost: false,
block: false,
htmlType: 'button' as ButtonProps['htmlType'],
};
Button.Group = Group;
Button.__ANT_BUTTON = true;
export default Button; export default Button;

View File

@ -1,9 +1,7 @@
import Button from './button'; import Button from './button';
import ButtonGroup from './button-group';
export { ButtonProps, ButtonShape, ButtonType } from './button'; export { ButtonProps, ButtonShape, ButtonType } from './button';
export { ButtonGroupProps } from './button-group'; export { ButtonGroupProps } from './button-group';
export { SizeType as ButtonSize } from '../config-provider/SizeContext'; export { SizeType as ButtonSize } from '../config-provider/SizeContext';
Button.Group = ButtonGroup;
export default Button; export default Button;