手把手搭建基于React的前端UI库 (四)-- 布局组件

前言

        承接上一篇# 手把手搭建基于React的前端UI库 (三)-- 基础组件Icon和Button。我们继续添加组件,本次记录的是布局组件Box和Combine。

        本文的代码展示的是主要的核心示意代码,全部代码见仓库:Gitee仓库

Box

        关于页面布局,最流行的便是flex布局,Box组件便是对flex布局进行的一次封装。废话不多说,上代码。在src/component文件夹下新建文件夹Box,在其中新建文件index.tsx

export interface BoxProps {
  children?: ReactNode;
  direction?: 'row' | 'row-reverse' | 'column' | 'column-reverse';
  wrap?: 'nowrap' | 'wrap' | 'wrap-reverse';
  alignItems?: 'center' | 'flex-start' | 'flex-end' | 'stretch';
  alignContent?: 'center' | 'flex-start' | 'flex-end' | 'space-between' | 'space-around';
  justifyContent?:
    | 'center'
    | 'flex-start'
    | 'flex-end'
    | 'space-between'
    | 'space-around'
    | 'stretch';
  padding?: number | string;
  width?: string;
  height?: string;
  flex?: string;
}

class Box extends PureComponent<BoxProps> {
  render() {
    const box = <BoxWrap {...this.props} />;

    return box;
  }
}

...

复制代码

        上边定义了一些接收参数,其与flex的css参数是对应的,我们就每一个具体的参数,在代码里进行实现便可以完成该组件了。接下来实现BoxWrap组件:

import styled from '@emotion/styled';
import { css } from '@emotion/core';

export const BoxWrap = styleWrap({
  className: prefixCls,
})(
  styled('div')((props: any) => {
      // 要实现的内容
  }),
);
复制代码

        架子已经搭起来,老样子,仍然使用styleWrap包裹一层公共样式,内部用一个div承接,中间要实现的内容应该返回要使用的css,这边是核心内容:

...
const {
    children,
    direction,
    wrap,
    justifyContent,
    alignItems,
    alignContent,
    span,
    flex,
    width,
    height,
    padding,
} = props;

...

return css`
    box-sizing: border-box;
    
    // direction
    ${direction != null &&
      css`
        flex-direction: ${direction};
      `}

    // wrap
    ${wrap != null &&
      css`
        flex-wrap: ${wrap};
      `}
      
    // justifyContent
    ${justifyContent != null &&
      css`
        justify-content: ${justifyContent};
      `}
      
    // ...
`
复制代码

        可以看到,只是简单加一个css便可以实现,相当简单。需要解释的是children的属性并没有公开声明,而是传递给styled生成的组件后隐式的渲染了。

        创建完毕后,在组件外便可以这样实现:

<Box direction="column" justifyContent="center">
    <div>1</div>
    <div>2</div>
    <div>3</div>
</Box>
复制代码

Combine

        Combine组件用于组合不同的组件,类似Box,仍然是样式为主的组件,是为了布局方便的,对css进行的封装。我们不使用flex,使用一个大div包裹一堆inline-block元素即可。在src/components下新建文件夹Combine,在文件夹下新建文件index.tsx

const Combine = ({
  children,
  sharedProps = {},
  spacing = 'smart',
  separator,
  ...rest
}: CombineProps) => {
  const { size = 'md' } = sharedProps;
  let isFirstItem: boolean;
  return (
    <CombineWrap spacing={spacing} {...rest}>
        {React.Children.map(children, (child) => (
            // 遍历child
            <>
                // 放置分隔符
                {separator && !isFirstItem ? <span className={separatorCls} key="separator">
                    {separator}
                  </span> : null
                }
                // 放置子元素
                <div className={itemCls}>
                    {React.cloneElement(child)}
                    ...
                </div>
            </>
        )}
    </CombineWrap>
  )
}
复制代码

上边代码中定义了子元素和分隔符的类:

export const itemCls = prefixCls + '-item';
export const separatorCls = prefixCls + '-separator';
复制代码

        组件接收spacing参数来控制相关联组件之间的距离,同时还可以设置separator来作为自定义分隔符。来看一下组件CombineWrap的实现:

import styled from '@emotion/styled';
import { css } from '@emotion/core';

export const CombineWrap = styledWrap<{ spacing: string }>({
  // 接受自定义class
  className: prefixCls,
})(
  // 生成一个自定义样式包裹的div
  styled('div')((props) => {
    const {
      spacing,
      theme: { designTokens: DT },
    } = props;
    
    // spacingMap为designTokens里定义的常量
    const space = (DT as any)[spacingMap[spacing]];

    return css`
      display: inline-block;  // inlink使得各个子元素平铺排列
      vertical-align: middle;
      
      // 子元素样式
      > .${itemCls} { 
        &:focus {
          z-index: 2;
        }
        &:hover {
          z-index: 3;
        }
      }
      
      // 子元素下一个兄弟,分隔符下一个子元素,子元素下一个分隔符 统一都加上左边距,这里就是space的用法
      > .${itemCls}+.${itemCls}, > .${separatorCls}+.${itemCls}, > .${itemCls}+.${separatorCls} {
        margin-left: ${space};
      }
    `;
  }),
);

复制代码

关于spacing常量,可以在theme主体中定义常量:

扫描二维码关注公众号,回复: 13761436 查看本文章
T_SPACING_COMMON_XS: '4px',
T_SPACING_COMMON_SM: '8px',
T_SPACING_COMMON_MD: '12px',
T_SPACING_COMMON_LG: '16px',
T_SPACING_COMMON_XLG: '20px',
T_SPACING_COMMON_XXLG: '24px',
T_SPACING_COMMON_XXXLG: '32px',
复制代码

然后我们在项目中使用一下:

<Combine>
  <Button styleType="primary">按钮组展示</Button>
  <Button>按钮组展示</Button>
  <Button>按钮组展示</Button>
  <Button disabled>按钮组展示</Button>
</Combine>
复制代码

至此,布局组件BoxCombine已经介绍完毕~ 更多组件参照代码仓库:Gitee仓库

参考

React-components
Antd之Space和Grid

猜你喜欢

转载自juejin.im/post/7082686023213252616