refactor(breadcrumb): rewrite with hook (#24512)

* refactor(breadcrumb): rewrite with hook

* Update BreadcrumbItem.tsx
This commit is contained in:
Tom Xu 2020-05-28 15:22:00 +08:00 committed by GitHub
parent 81bde547cd
commit 56c42e496b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 132 additions and 165 deletions

View File

@ -1,11 +1,10 @@
import * as React from 'react';
import classNames from 'classnames';
import toArray from 'rc-util/lib/Children/toArray';
import omit from 'omit.js';
import BreadcrumbItem from './BreadcrumbItem';
import BreadcrumbSeparator from './BreadcrumbSeparator';
import Menu from '../menu';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import devWarning from '../_util/devWarning';
import { Omit } from '../_util/type';
import { cloneElement } from '../_util/reactNode';
@ -49,41 +48,48 @@ function defaultItemRender(route: Route, params: any, routes: Route[], paths: st
return isLastItem ? <span>{name}</span> : <a href={`#/${paths.join('/')}`}>{name}</a>;
}
export default class Breadcrumb extends React.Component<BreadcrumbProps, any> {
static Item: typeof BreadcrumbItem;
const getPath = (path: string, params: any) => {
path = (path || '').replace(/^\//, '');
Object.keys(params).forEach(key => {
path = path.replace(`:${key}`, params[key]);
});
return path;
};
static Separator: typeof BreadcrumbSeparator;
const addChildPath = (paths: string[], childPath: string = '', params: any) => {
const originalPaths = [...paths];
const path = getPath(childPath, params);
if (path) {
originalPaths.push(path);
}
return originalPaths;
};
static defaultProps = {
separator: '/',
};
interface BreadcrumbInterface extends React.FC<BreadcrumbProps> {
Item: typeof BreadcrumbItem;
Separator: typeof BreadcrumbSeparator;
}
getPath = (path: string, params: any) => {
path = (path || '').replace(/^\//, '');
Object.keys(params).forEach(key => {
path = path.replace(`:${key}`, params[key]);
});
return path;
};
const Breadcrumb: BreadcrumbInterface = ({
prefixCls: customizePrefixCls,
separator = '/',
style,
className,
routes,
children,
itemRender = defaultItemRender,
params = {},
...restProps
}) => {
const { getPrefixCls, direction } = React.useContext(ConfigContext);
addChildPath = (paths: string[], childPath: string = '', params: any) => {
const originalPaths = [...paths];
const path = this.getPath(childPath, params);
if (path) {
originalPaths.push(path);
}
return originalPaths;
};
genForRoutes = ({
routes = [],
params = {},
separator,
itemRender = defaultItemRender,
}: BreadcrumbProps) => {
let crumbs;
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
if (routes && routes.length > 0) {
// generated by route
const paths: string[] = [];
return routes.map(route => {
const path = this.getPath(route.path, params);
crumbs = routes.map(route => {
const path = getPath(route.path, params);
if (path) {
paths.push(path);
@ -95,7 +101,7 @@ export default class Breadcrumb extends React.Component<BreadcrumbProps, any> {
<Menu>
{route.children.map(child => (
<Menu.Item key={child.path || child.breadcrumbName}>
{itemRender(child, params, routes, this.addChildPath(paths, child.path, params))}
{itemRender(child, params, routes, addChildPath(paths, child.path, params))}
</Menu.Item>
))}
</Menu>
@ -108,58 +114,40 @@ export default class Breadcrumb extends React.Component<BreadcrumbProps, any> {
</BreadcrumbItem>
);
});
};
} else if (children) {
crumbs = toArray(children).map((element: any, index) => {
if (!element) {
return element;
}
renderBreadcrumb = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
let crumbs;
const {
prefixCls: customizePrefixCls,
separator,
style,
className,
routes,
children,
...restProps
} = this.props;
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
if (routes && routes.length > 0) {
// generated by route
crumbs = this.genForRoutes(this.props);
} else if (children) {
crumbs = toArray(children).map((element: any, index) => {
if (!element) {
return element;
}
devWarning(
element.type &&
(element.type.__ANT_BREADCRUMB_ITEM === true ||
element.type.__ANT_BREADCRUMB_SEPARATOR === true),
'Breadcrumb',
"Only accepts Breadcrumb.Item and Breadcrumb.Separator as it's children",
);
devWarning(
element.type &&
(element.type.__ANT_BREADCRUMB_ITEM === true ||
element.type.__ANT_BREADCRUMB_SEPARATOR === true),
'Breadcrumb',
"Only accepts Breadcrumb.Item and Breadcrumb.Separator as it's children",
);
return cloneElement(element, {
separator,
key: index,
});
return cloneElement(element, {
separator,
key: index,
});
}
const breadcrumbClassName = classNames(className, prefixCls, {
[`${prefixCls}-rtl`]: direction === 'rtl',
});
return (
<div
className={breadcrumbClassName}
style={style}
{...omit(restProps, ['itemRender', 'linkRender', 'nameRender', 'params'])}
>
{crumbs}
</div>
);
};
render() {
return <ConfigConsumer>{this.renderBreadcrumb}</ConfigConsumer>;
}
}
const breadcrumbClassName = classNames(className, prefixCls, {
[`${prefixCls}-rtl`]: direction === 'rtl',
});
return (
<div className={breadcrumbClassName} style={style} {...restProps}>
{crumbs}
</div>
);
};
Breadcrumb.Item = BreadcrumbItem;
Breadcrumb.Separator = BreadcrumbSeparator;
export default Breadcrumb;

View File

@ -1,9 +1,8 @@
import * as React from 'react';
import DownOutlined from '@ant-design/icons/DownOutlined';
import omit from 'omit.js';
import DropDown, { DropDownProps } from '../dropdown/dropdown';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
export interface BreadcrumbItemProps {
prefixCls?: string;
@ -13,53 +12,24 @@ export interface BreadcrumbItemProps {
dropdownProps?: DropDownProps;
onClick?: React.MouseEventHandler<HTMLAnchorElement | HTMLSpanElement>;
}
export default class BreadcrumbItem extends React.Component<BreadcrumbItemProps, any> {
static __ANT_BREADCRUMB_ITEM = true;
static defaultProps = {
separator: '/',
};
renderBreadcrumbItem = ({ getPrefixCls }: ConfigConsumerProps) => {
const { prefixCls: customizePrefixCls, separator, children, ...restProps } = this.props;
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
let link;
if ('href' in this.props) {
link = (
<a className={`${prefixCls}-link`} {...omit(restProps, ['overlay'])}>
{children}
</a>
);
} else {
link = (
<span className={`${prefixCls}-link`} {...omit(restProps, ['overlay'])}>
{children}
</span>
);
}
// wrap to dropDown
link = this.renderBreadcrumbNode(link, prefixCls);
if (children) {
return (
<span>
{link}
{separator && separator !== '' && (
<span className={`${prefixCls}-separator`}>{separator}</span>
)}
</span>
);
}
return null;
};
interface BreadcrumbItemInterface extends React.FC<BreadcrumbItemProps> {
__ANT_BREADCRUMB_ITEM: boolean;
}
const BreadcrumbItem: BreadcrumbItemInterface = ({
prefixCls: customizePrefixCls,
separator,
children,
overlay,
dropdownProps,
...restProps
}) => {
const { getPrefixCls } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
/**
* if overlay is have
* Wrap a DropDown
*/
renderBreadcrumbNode = (breadcrumbItem: React.ReactNode, prefixCls: string) => {
const { overlay, dropdownProps } = this.props;
const renderBreadcrumbNode = (breadcrumbItem: React.ReactNode) => {
if (overlay) {
return (
<DropDown overlay={overlay} placement="bottomCenter" {...dropdownProps}>
@ -73,7 +43,36 @@ export default class BreadcrumbItem extends React.Component<BreadcrumbItemProps,
return breadcrumbItem;
};
render() {
return <ConfigConsumer>{this.renderBreadcrumbItem}</ConfigConsumer>;
let link;
if ('href' in restProps) {
link = (
<a className={`${prefixCls}-link`} {...restProps}>
{children}
</a>
);
} else {
link = (
<span className={`${prefixCls}-link`} {...restProps}>
{children}
</span>
);
}
}
// wrap to dropDown
link = renderBreadcrumbNode(link);
if (children) {
return (
<span>
{link}
{separator && separator !== '' && (
<span className={`${prefixCls}-separator`}>{separator}</span>
)}
</span>
);
}
return null;
};
BreadcrumbItem.__ANT_BREADCRUMB_ITEM = true;
export default BreadcrumbItem;

View File

@ -1,17 +1,17 @@
import * as React from 'react';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
export default class BreadcrumbSeparator extends React.Component<any> {
static __ANT_BREADCRUMB_SEPARATOR = true;
renderSeparator = ({ getPrefixCls }: ConfigConsumerProps) => {
const { children } = this.props;
const prefixCls = getPrefixCls('breadcrumb');
return <span className={`${prefixCls}-separator`}>{children || '/'}</span>;
};
render() {
return <ConfigConsumer>{this.renderSeparator}</ConfigConsumer>;
}
interface BreadcrumbSeparatorInterface extends React.FC {
__ANT_BREADCRUMB_SEPARATOR: boolean;
}
const BreadcrumbSeparator: BreadcrumbSeparatorInterface = ({ children }) => {
const { getPrefixCls } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('breadcrumb');
return <span className={`${prefixCls}-separator`}>{children || '/'}</span>;
};
BreadcrumbSeparator.__ANT_BREADCRUMB_SEPARATOR = true;
export default BreadcrumbSeparator;

View File

@ -111,21 +111,6 @@ describe('Breadcrumb', () => {
it('should accept undefined routes', () => {
const wrapper = render(<Breadcrumb routes={undefined} />);
expect(wrapper).toMatchSnapshot();
})
it('props#linkRender and props#nameRender do not warn anymore', () => {
const linkRender = jest.fn();
const nameRender = jest.fn();
mount(
<Breadcrumb linkRender={linkRender} nameRender={nameRender}>
<Breadcrumb.Item />
<Breadcrumb.Item>xxx</Breadcrumb.Item>
</Breadcrumb>,
);
expect(errorSpy.mock.calls.length).toBe(0);
expect(linkRender).not.toHaveBeenCalled();
expect(nameRender).not.toHaveBeenCalled();
});
it('should support custom attribute', () => {

View File

@ -73,7 +73,6 @@ exports[`react router react router 3 1`] = `
},
]
}
separator="/"
>
<div
className="ant-breadcrumb"

View File

@ -1,10 +1,6 @@
import Breadcrumb from './Breadcrumb';
import BreadcrumbItem from './BreadcrumbItem';
import BreadcrumbSeparator from './BreadcrumbSeparator';
export { BreadcrumbProps } from './Breadcrumb';
export { BreadcrumbItemProps } from './BreadcrumbItem';
Breadcrumb.Item = BreadcrumbItem;
Breadcrumb.Separator = BreadcrumbSeparator;
export default Breadcrumb;