antd源码-Button解析
Button render
在Button的组件中的index函数中,我们从antd引入button的时候是从index.tsx这个文件引入的
import Button from './button';
import ButtonGroup from './button-group';
export { ButtonProps, ButtonShape, ButtonSize, ButtonType } from './button';
export { ButtonGroupProps } from './button-group';
Button.Group = ButtonGroup;
export default Button;
从这里可以看出为什么我们可以在我们的业务组件中使用<Button.Group>
button.tsx render函数
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
render() {
return <ConfigConsumer>{this.renderButton}</ConfigConsumer>;
}
ConfigConsumer
ConfigConsumer是利用的React.createReactContext(),创建上下文的服务消费者。在…/config-provider可以看到核心。
import createReactContext from '@ant-design/create-react-context';
export const ConfigContext = createReactContext<ConfigConsumerProps>({
// We provide a default function for Context without provider
getPrefixCls: (suffixCls: string, customizePrefixCls?: string) => {
if (customizePrefixCls) return customizePrefixCls;
return `ant-${suffixCls}`;
},
renderEmpty: defaultRenderEmpty,
});
export const ConfigConsumer = ConfigContext.Consumer;
React.createReactContext()运用的@ant-design/create-react-context里面的方法。它创建了一个上下文容器,并且初始化了共享数据,React.createReactContext()可以参考
@ant-design/create-react-context是对React.createReactContext() 是对的封装,
this.renderButton
renderButton = ({ getPrefixCls, autoInsertSpaceInButton }: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
type,
shape,
size,
className,
children,
icon,
ghost,
block,
...rest
} = this.props;
const { loading, hasTwoCNChar } = this.state;
const prefixCls = getPrefixCls('btn', customizePrefixCls);
const autoInsertSpace = autoInsertSpaceInButton !== false;
// large => lg
// small => sm
let sizeCls = '';
switch (size) {
case 'large':
sizeCls = 'lg';
break;
case 'small':
sizeCls = 'sm';
break;
default:
break;
}
const iconType = loading ? 'loading' : icon;
// 用于渲染类名
const classes = classNames(prefixCls, className, {
[`${prefixCls}-${type}`]: type,
[`${prefixCls}-${shape}`]: shape,
[`${prefixCls}-${sizeCls}`]: sizeCls,
[`${prefixCls}-icon-only`]: !children && children !== 0 && iconType,
[`${prefixCls}-loading`]: !!loading,
[`${prefixCls}-background-ghost`]: ghost,
[`${prefixCls}-two-chinese-chars`]: hasTwoCNChar && autoInsertSpace,
[`${prefixCls}-block`]: block,
});
const iconNode = iconType ? <Icon type={iconType} /> : null;
// 用于处理多个孩子的情况
const kids =
children || children === 0
? spaceChildren(children, this.isNeedInserted() && autoInsertSpace)
: null;
const linkButtonRestProps = omit(rest as AnchorButtonProps, ['htmlType', 'loading']);
如果有传递href属性。返回的dom是a标签类型的。
if (linkButtonRestProps.href !== undefined) {
return (
<a
{...linkButtonRestProps}
className={classes}
onClick={this.handleClick}
ref={this.saveButtonRef}
>
{iconNode}
{kids}
</a>
);
}
// React does not recognize the `htmlType` prop on a DOM element. Here we pick it out of `rest`.
const { htmlType, ...otherProps } = rest as NativeButtonProps;
// 这是普通的button
const buttonNode = (
<button
{...(omit(otherProps, ['loading']) as NativeButtonProps)}
type={htmlType}
className={classes}
onClick={this.handleClick}
ref={this.saveButtonRef}
>
{iconNode}
{kids}
</button>
);
// 类型是链接类型直接返回不添加点击动画
if (type === 'link') {
return buttonNode;
}
/// wave 点击动画容器
return <Wave>{buttonNode}</Wave>;
};
wave水波动画
运用的是css-animation插件,添加的是css动画,
export default class Wave extends React.Component<{ insertExtraNode?: boolean }> {
private instance?: {
cancel: () => void;
};
private extraNode: HTMLDivElement;
private clickWaveTimeoutId: number;
private animationStartId: number;
private animationStart: boolean = false;
private destroy: boolean = false;
private csp?: CSPConfig;
componentDidMount() {
const node = findDOMNode(this) as HTMLElement;
if (!node || node.nodeType !== 1) {
return;
}
// 组件加载绑定动画点击事件
this.instance = this.bindAnimationEvent(node);
}
componentWillUnmount() {
if (this.instance) {
this.instance.cancel();
}
if (this.clickWaveTimeoutId) {
clearTimeout(this.clickWaveTimeoutId);
}
this.destroy = true;
}
// 具体点击函数,作用是添加内嵌元素效果或者加入after作为动画效果
onClick = (node: HTMLElement, waveColor: string) => {
if (!node || isHidden(node) || node.className.indexOf('-leave') >= 0) {
return;
}
const { insertExtraNode } = this.props;
// 创建内嵌元素
this.extraNode = document.createElement('div');
const { extraNode } = this;
extraNode.className = 'ant-click-animating-node';
const attributeName = this.getAttributeName();
node.setAttribute(attributeName, 'true');
// Not white or transparnt or grey
// 创建style标签,
styleForPesudo = styleForPesudo || document.createElement('style');
if (
waveColor &&
waveColor !== '#ffffff' &&
waveColor !== 'rgb(255, 255, 255)' &&
isNotGrey(waveColor) &&
!/rgba\(\d*, \d*, \d*, 0\)/.test(waveColor) && // any transparent rgba color
waveColor !== 'transparent'
) {
// Add nonce if CSP exist
if (this.csp && this.csp.nonce) {
styleForPesudo.nonce = this.csp.nonce;
}
// 样式
extraNode.style.borderColor = waveColor;
styleForPesudo.innerHTML = `
[ant-click-animating-without-extra-node='true']::after, .ant-click-animating-node {
--antd-wave-shadow-color: ${waveColor};
}`;
if (!document.body.contains(styleForPesudo)) {
document.body.appendChild(styleForPesudo);
}
}
if (insertExtraNode) {
node.appendChild(extraNode);
}
TransitionEvents.addStartEventListener(node, this.onTransitionStart);
TransitionEvents.addEndEventListener(node, this.onTransitionEnd);
};
onTransitionStart = (e: AnimationEvent) => {
if (this.destroy) return;
const node = findDOMNode(this) as HTMLElement;
if (!e || e.target !== node) {
return;
}
if (!this.animationStart) {
this.resetEffect(node);
}
};
// 检测到动画结束,恢复属性至初始化
onTransitionEnd = (e: AnimationEvent) => {
if (!e || e.animationName !== 'fadeEffect') {
return;
}
this.resetEffect(e.target as HTMLElement);
};
getAttributeName() {
const { insertExtraNode } = this.props;
return insertExtraNode ? 'ant-click-animating' : 'ant-click-animating-without-extra-node';
}
bindAnimationEvent = (node: HTMLElement) => {
if (
!node ||
!node.getAttribute ||
node.getAttribute('disabled') ||
node.className.indexOf('disabled') >= 0
) {
return;
}
// 定义点击
const onClick = (e: MouseEvent) => {
// Fix radio button click twice
if ((e.target as HTMLElement).tagName === 'INPUT' || isHidden(e.target as HTMLElement)) {
return;
}
this.resetEffect(node);
// Get wave color from target
const waveColor =
getComputedStyle(node).getPropertyValue('border-top-color') || // Firefox Compatible
getComputedStyle(node).getPropertyValue('border-color') ||
getComputedStyle(node).getPropertyValue('background-color');
this.clickWaveTimeoutId = window.setTimeout(() => this.onClick(node, waveColor), 0);
raf.cancel(this.animationStartId);
this.animationStart = true;
// Render to trigger transition event cost 3 frames. Let's delay 10 frames to reset this.
this.animationStartId = raf(() => {
this.animationStart = false;
}, 10);
};
//绑定点击时间
node.addEventListener('click', onClick, true);
return {
cancel: () => {
node.removeEventListener('click', onClick, true);
},
};
};
// 初始化动画初始参数
resetEffect(node: HTMLElement) {
if (!node || node === this.extraNode || !(node instanceof Element)) {
return;
}
const { insertExtraNode } = this.props;
//绑定属性-自定义属性 有内嵌元素动画和运用after动画
const attributeName = this.getAttributeName();
// 设置这个属性未false
node.setAttribute(attributeName, 'false'); // edge has bug on `removeAttribute` #14466
// 设置style标签内的内容未空
if (styleForPesudo) {
styleForPesudo.innerHTML = '';
}
// 内嵌元素的话。删除这个内嵌元素
if (insertExtraNode && this.extraNode && node.contains(this.extraNode)) {
node.removeChild(this.extraNode);
}
// 移除transition动画监听函数
TransitionEvents.removeStartEventListener(node, this.onTransitionStart);
TransitionEvents.removeEndEventListener(node, this.onTransitionEnd);
}
renderWave = ({ csp }: ConfigConsumerProps) => {
const { children } = this.props;
this.csp = csp;
return children;
};
render() {
return <ConfigConsumer>{this.renderWave}</ConfigConsumer>;
}
}
总结
- button组件应用react的createReactContext,提供共享数据,consumer消费,获得共享数据,这一个应用在处理一些业务逻辑的时候可以应用,
- button组件的编写和我们自己些button没有太大的区别,根据传入参数的不同。渲染不同的内容,
- 容器不只是作为组件,可以作为空的容器,对容器内的内容做动画或者其他处理,类似组件的封装效果。