对不起,antd的Grid组件,element的Layout组件?过时了!

11,149 阅读3分钟

前言

这个组件github地址:github.com/lio-mengxia…

现在国内国外主流B端框架都会有一个叫布局的组件,如下图:

antd的布局组件示例如下:

image.png

element plus 布局组件示例如下:

image.png

用过的同学都知道,这些组件主要是帮助我们布局的,并且可以设置在不同屏幕尺寸下布局的样式。

组件的本质就是使用flex布局,假如我们要布局成如下样式,以上的布局组件实现起来比较麻烦,不妨你们尝试一下:

image.png

开始用felx写了一版,总感觉代码非常不优雅,条件判断很多,后来一想,我们是新项目,不兼容ie的,为啥不用grid布局呢,但是grid布局纯写css并不难,怎么把其封装成一个通用组件,并且使用起来要:

替代antd的grid组件! element plus 的layout组件!

问题就来了,市面上通用组件库都没这个组件啊,咋办,手写呗。参考开源项目 styled-css-grid,咋们用react函数式组件实现,并附有在线案例。因为代码只涉及到css封装,vue也可以借鉴然后封装自己的超强布局组件。

代码很优雅,只有两个文件。先看效果,再写源码, 一下所有案例的在线地址为: codesandbox.io/s/amazing-c…

案例一(这个案例的目的是说明我们组件具备子元素偏移的能力):

image.png

const cellStyle = {
  fontSize: "0.8em",
  border: "1px solid #999",
  background: "#f5f2f0",
  lineHeight: 1,
  color: "#905"
};
export default function App() {
  return (
    <Grid columns={3} height={200}>
      <Cell center middle style={cellStyle}>
        Top Left
      </Cell>
      <Cell center middle style={cellStyle} left={3}>
        Top Right
      </Cell>
      <Cell center middle style={cellStyle} left={2} top={2}>
        Middle
      </Cell>
      <Cell center middle style={cellStyle} top={3}>
        Bottom Left
      </Cell>
      <Cell center middle style={cellStyle} top={3} left={3}>
        Bottom Right
      </Cell>
    </Grid>
  );
}

案例2(这个案例说明我们的组件有任意分隔栅格布局的能力):

image.png

import { flatMap, range } from "lodash-es";

const cellStyle = {
  fontSize: "0.8em",
  border: "1px solid #999",
  background: "#f5f2f0",
  lineHeight: 1,
  color: "#905"
};

const rows = (counts) =>
  flatMap(counts, (number) =>
    range(number).map((i) => (
      <Cell center middle style={cellStyle} width={12 / number} key={`${number}_${i}`}>
        {i + 1}/{number}
      </Cell>
    ))
  );

const TraditionalGrid = () => (
  <article>
    <Grid columns={12} minRowHeight="45px">
      {rows([12, 6, 4, 2, 1])}
    </Grid>
  </article>
);

案例三(代表我们组件具有奇形怪状的能力):

image.png

当然我们也具备本身flex的左对齐,右对齐,中间对齐这些功能:

image.png

以下是源码,一起交流:

实现GridLayout组件

import React, { useMemo } from 'react';
import { frGetter, autoRows, formatAreas } from '../config/functions';

const GridLayout = ({
  columns,
  gap = '8px',
  columnGap,
  areas,
  minRowHeight,
  alignContent,
  rowGap,
  rows,
  justifyContent,
  flow,
  children,
  height = 'auto',
  style = {},
}: any) => {
  const mergedstyle = {
    display: 'grid', // 布局是grid布局
    height, // 设置容器高度
    gridAutoFlow: flow, // 设置容器内元素是从左往右(默认),还是从右往左
    gridAutoRows: autoRows(minRowHeight), // grid-auto-rows设置默认单元格高度
    gridTemplateRows: frGetter(rows), // 当传递一个数字时,它是指定行数的简写,平分高度,自适应。如果是例如100px,就是以这个数值为宽度
    gridTemplateColumns: frGetter(columns), // 当传递一个数字时,它是指定列数的简写,平分宽度,自适应。 默认值为 12,如果是例如100px,就是以这个数值为宽度
    columnGap, // 设置每个子元素之间列的间距
    rowGap, // 设置每个子元素之间行的间距
    areas: formatAreas(areas), // 传递一个字符串数组,例如 [“a a”,“b c”]。 默认不提供。
    justifyContent, // 决定整个内容区域在容器里面的水平位置(左中右)
    gridGap: gap, // 设置每个子元素之间的间距,默认8px
    alignContent, // 决定整个内容区域的垂直位置(上中下)
    ...style,
  };
  return <div style={mergedstyle}>{children}</div>;
};

export default React.memo(GridLayout);

Cell组件

import React from 'react';

const middleStyle = (middle) => {
 if (middle) {
   return {
     display: 'inline-flex',
     flexFlow: 'column wrap',
     justifyContent: 'center',
     justifySelf: 'stretch',
   };
 }
};

const Cell = ({ width = 1, height = 1, area, middle, style = {}, left, top, center, children }: any) => {
 const mergedstyle: any = {
   height: '100%',
   minWidth: 0,
   gridColumnEnd: `span ${width}`, // 使用 grid-column-end 属性设置网格元素跨越多少列,或者在哪一列结束。
   gridRowEnd: `span ${height}`, // grid-row-start 属性指定哪一行开始显示网格元素
   gridColumnStart: left, // grid-column-start 属性定义了网格元素从哪一列开始
   gridRowStart: top, // grid-row-end 属性指定哪一行停止显示网格元素,或设置跨越多少行
   textAlign: center && 'center',
   ...middleStyle(middle),
   ...style,
 };
 if (area) mergedstyle.gridArea = area;
 return <div style={mergedstyle}>{children}</div>;
};

export default React.memo(Cell);

工具函数:

export const autoRows = (minRowHeight = '20px') => `minmax(${minRowHeight}, auto)`;

export const frGetter = (value) => {
 if (!value) return;
 return typeof value === 'number' ? `repeat(${value}, 1fr)` : value;
};

export const formatAreas = (areas) => areas && areas.map((area) => `"${area}"`).join(' ');