前言
之前的组件库相关文章代码:
- Affix组件: [react组件库源码+ 单测解析(Affix 固钉组件)]
- Form组件:实现一个比ant-design更好form组件,可用于生产环境!
- GridLayout组件:秒杀ant design布局组件
继续分析react组件源码,代码来源于t-design的button和arco design(飞书的组件库)的ButtonGroup组件。
button组件dom结构解析
以下的RenderTag是指可能是button标签,div标签或者a标签。RenderTag的实现:
const RenderTag = useMemo(() => {
if (!tag && href) return 'a';
if (!tag && disabled) return 'div';
return tag || 'button';
}, [tag, href, disabled]);
为啥会有div标签呢?
因为禁用按钮 <button disabled>
无法显示 Popup 浮层信息,可通过修改 tag=div
解决这个问题。
然后a标签是为了有按钮可能会跳转页面,不属于button的功能范围了,所以支持了a标签的按钮渲染。下面接着分析一下RenderTag组件上的参数是啥意思。
<RenderTag
{...buttonProps}
href={href}
type={type}
ref={ref}
disabled={disabled || loading}
className={classNameProps}
onClick={!disabled && !loading ? onClick : undefined}
>
{iconNode}
{children && <span className={`${classPrefix}-button__text`}>{children}</span>}
{suffix && <span className={`${classPrefix}-button__suffix`}>{suffix}</span>}
</RenderTag>
- buttonProps是什么呢,是透传给button元素(div或者a标签同理)的值,比如title属性。
- 跳转地址。href 存在时,按钮标签默认使用
<a>
渲染;如果指定了tag
则使用指定的标签渲染 - type:按钮类型。可选项:submit/reset/button
- ref,用来获取RenderTag的Ref的
- disabled: button的禁用状态
- onClick:点击事件的注册函数,注意当按钮是disabled和loading态的话,点击是无效的
- iconNode是icon的组件,比如svg
实现的代码如下:
let iconNode = icon;
if (loading) iconNode = <Loading loading={loading} inheritColor={true} />;
如果是loading态,就渲染Loading组件,这个组件细节我们不用去了解,后面的文章会专门写这个组件。Loading组价你现在看来可以理解为简单的一个转菊花
-
children一般情况是渲染button文字,当然也可以是ReactNode
-
suffix 右侧内容,可用于定义右侧图标,
button组件到这里就差不多了,最后我们看下其他参数,基本上都是css的表现参数。
const classNameProps = classNames(
className,
[
`${classPrefix}-button`, // button的基本样式
// 组件风格,依次为默认色、品牌色、危险色、警告色、成功色。可选项:default/primary/danger/warning/success
`${classPrefix}-button--theme-${renderTheme}`,
// 按钮形式,基础、线框、虚线、文字。可选项:base/outline/dashed/text
`${classPrefix}-button--variant-${variant}`,
],
{
// 按钮形状,有 4 种:长方形、正方形、圆角长方形、圆形。可选项:rectangle/square/round/circle
[`${classPrefix}-button--shape-${shape}`]: shape !== 'rectangle',
// 是否为幽灵按钮(镂空按钮)
[`${classPrefix}-button--ghost`]: ghost,
// 是否是loading态 主要是 设置 cursor: default;
[`${classPrefix}-is-loading`]: loading,
// 是否是disabled态,主要是设置颜色
[`${classPrefix}-is-disabled`]: disabled,
// 设置按钮的width为100%,display:block
[`${classPrefix}-size-full-width`]: block,
},
);
接着我们看下button的单测,我举一个例子,因为这个组件太简单了,就举一反三了:
import React from 'react';
// render是用来渲染组件的
// fireEvent是用来触发dom事件的
// vi相当于jest,拥有比如vi.fn -> jest.fn,几乎一样的api
import { render, fireEvent, vi } from '@test/utils';
import Button from '../Button';
describe('Button 组件测试', () => {
const ButtonText = '按钮组件';
test('create', async () => {
// 模拟一个click函数
const clickFn = vi.fn();
// container是渲染的dom引用
// queryByText是用来寻找react组件的dom元素的
const { container, queryByText } = render(<Button onClick={clickFn}>{ButtonText}</Button>);
expect(container.firstChild.classList.contains('t-button--variant-base')).toBeTruthy();
// 意思是渲染后是否按钮出现在document文档中
expect(queryByText(ButtonText)).toBeInTheDocument();
// 触发一次点击事件
fireEvent.click(container.firstChild);
// 检测是否点击事件被调用了
expect(clickFn).toBeCalledTimes(1);
});
// 这里主要检测在disabled传参为true的情况下,click事件是否没有被调用
test('disabled', async () => {
const clickFn = vi.fn();
const { container } = render(<Button disabled onClick={clickFn} />);
expect(container.firstChild.nodeName).toBe('DIV');
fireEvent.click(container.firstChild);
expect(clickFn).toBeCalledTimes(0);
});
});
ButtonGroup按钮原理
我们看下ButtonGroup怎么用
<ButtonGroup>
<Button type='primary' icon={<IconStar />} />
<Button type='primary' icon={<IconMessage />} />
<Button type='primary' icon={<IconSettings />} />
</ButtonGroup>
效果是啥呢,如下图:
源码如下:
function Group(props: ButtonGroupProps, ref) {
const { className, style, children } = props;
return (
<div ref={ref} className={classNames} style={style}>
{children}
</div>
);
}
是不是很简单,ButtonGroup主要是帮助下面的button组件完善css样式而已,我们看下css怎么写呢
.btn-group {
display: inline-block;
}
按钮之间是有一个竖线的,我们的实现借助了:not和:last-child语法
.btn-group .btn:not(:last-child) {
border-right: 1px solid rgb(var(--primary-5));
}
接着我们需要把第一个元素右上radius和右下radius置为0,同理,最后一个元素也是如此
.btn-group .btn:first-child {
border-radius: var(--border-radius-small) 0 0 var(--border-radius-small);
}
.btn-group .btn:last-child {
border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0;
}
.btn-group .btn:not(:first-child):not(:last-child) {
border-radius: 0;
}
好了,结束。这个单测其实很简单了,就是测试如果在Group中,这些元素是否有对应css类名。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
暂无评论内容