从Antd 源码到自我实现之 Grid 栅格系统

Antd Grid 简述

Grid 布局的栅格化系统,我们是基于行(row)和列(col)来定义信息区块的外部框架,以保证页面的每个区域能够稳健地排布起来。下面简单介绍一下它的工作原理:

  • 通过row在水平方向建立一组column(简写col)

  • 你的内容应当放置于col内,并且,只有col可以作为row的直接元素

  • 栅格系统中的列是指1到24的值来表示其跨越的范围。例如,三个等宽的列可以使用.col-8来创建

  • 如果一个row中的col总和超过 24,那么多余的col会作为一个整体另起一行排列

此外, Grid 还支持 Flex 布局。

核心功能点提取

从Antd 源码到自我实现之 Grid 栅格系统_第1张图片

核心实现

Grid 由 Row 和 Col 组成。

// Grid.tsx
import * as React from 'react';

import Col from './Col';
import Row from './Row';

export default class Grid extends React.Component {
    static Row = Row;
    static Col = Col;
}

Row

先来看一下代码实现:

export default class Row extends Component<RowProps, any> {
    static propTypes = {
        align: PropTypes.oneOf(['top', 'center', 'bottom']),
        className: PropTypes.string,
        gutter: PropTypes.number,
        justify: PropTypes.oneOf(['start', 'end', 'center', 'space-around', 'space-between']),
        style: PropTypes.object,
        type: PropTypes.oneOf(['flex'])
    };

    static defaultProps = {
        align: 'top',
        gutter: 0,
        justify: 'start'
    };

    constructor(props) {
        super(props);
    }

    render() {
        const {
            type,
            justify,
            align,
            className,
            style,
            children,
            gutter,
            ...otherProps
          } = this.props;

        const classes = classNames(className, {
                [prefixCls]: !type,
                [`${prefixCls}-${type}`]: type,
                [`${prefixCls}-${type}-${justify}`]: type && justify,
                [`${prefixCls}-${type}-align-${align}`]: type && align
        });

        return (
            <RowContext.Provider value={{gutter}}>
              <div {...otherProps} className={classes} style={style}>
              	<!-- children 即为 Col 集合 -->
                {children}
              </div>
            </RowContext.Provider>
        );
    }
}

从代码可以发现,Row 作为 Col 上层组件,主要提供以下功能点:

  1. 提供一个 Col 可以获取的 context,主要传递 gutter (栅格间距)。 gutter 的本质实际上是为 Col 提供一个 padding-left 和 padding-right,它们分别取值 gutter/2
  2. 作为 Flex 的容器,实现 flex 布局,当 type = ‘flex’ 时
    1) justify 实际对应于 css 中的 justify-content
    2) align 实际对应于 css 中的 align-item

Col

先看一下 Col 的代码实现:

export default class Col extends React.Component<ColProps, {}> {
    static propTypes = {
        children: PropTypes.node,
        className: PropTypes.string,
        offset: PropTypes.number,
        order: PropTypes.number,
        pull: PropTypes.number,
        push: PropTypes.number,
        span: PropTypes.number,
        style: PropTypes.object
    };

    static defaultProps = {
        span: 1
    };

    constructor(props) {
        super(props);
    }

    render() {
        const {
            span,
            order,
            offset,
            push,
            pull,
            className,
            children,
            xs,
            sm,
            md,
            xl,
            lg,
            xxl,
            ...otherProps
          } = this.props;

        let sizeClassObj = {};
        ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'].forEach(size => {
            const props = this.props;
            let sizeProps: ColSize = {};
            if (typeof props[size] === 'number') {
                sizeProps.span = props[size];
            } else if (typeof props[size] === 'object') {
                sizeProps = props[size] || {};
            }

            sizeClassObj = {
                ...sizeClassObj,
                [`${classPrefix}-${size}-${sizeProps.span}`]: !!sizeProps.span,
                [`${classPrefix}-${size}-order-${sizeProps.order}`]: !!sizeProps.order,
                [`${classPrefix}-${size}-offset-${sizeProps.offset}`]: !!sizeProps.offset,
                [`${classPrefix}-${size}-push-${sizeProps.push}`]: !!sizeProps.push,
                [`${classPrefix}-${size}-pull-${sizeProps.pull}`]: !!sizeProps.pull
            };
        });

        const classes = classNames(classPrefix, className, {
            [`${classPrefix}-${span}`]: !!span,
            [`${classPrefix}-pull-${pull}`]: !!pull,
            [`${classPrefix}-push-${push}`]: !!push,
            [`${classPrefix}-offset-${offset}`]: !!offset,
            [`${classPrefix}-order-${order}`]: !!order
        }, sizeClassObj);

        return (
            <RowContext.Consumer>
                {({gutter}) => {
                    let style = otherProps.style;
                    if (gutter > 0) {
                    style = {
                        paddingLeft: gutter / 2,
                        paddingRight: gutter / 2,
                        ...style
                    };
                    }
                    return (
                    <div {...otherProps} style={style} className={classes}>
                        {children}
                    </div>
                    );
                }}
            </RowContext.Consumer>
        );
    }
}

对照代码介绍一下 Col 中几个核心 prop 的实现方式:

  1. 每个 Col 都是 float: left,这样在非 flex 可以保证每个 col 依次排列
  2. 根据 Bootstrap 的 响应式设计,预设六个响应尺寸:xs sm md lg xl xxl,这个可以参照文档,就不细说了
  3. span 栅格占位格数 : 原理就是将 Col 的 width 按照 24 栅格计算的百分比. (例如 span = 8, 则 width = 8 / 24 = 33.33333%)
// span
.cols-rules (@n, @i) {
    width: (@i * 100% / @n);
}

.generate-cols(@n, @i: 1, @screenSize: default) when (@i =< @n) {
    &-@{i} {
        .cols-rules(@n, @i);
    }

    .generate-cols(@n, (@i + 1));
}
  • gutter : Row 中设置的 gutter 会在 Col 之间生成值为 gutter/2 的 padding-left 和 padding-right

  • offset 列向右偏移 : 本质为通过 margin-left 使 Col 发生偏移. Grid 是 24 栅格系统,因此 n = 24. 首先在 less 文件中用 mixin 预先生成 offset 从 1 到 24 的 margin-left。根据用户设定的不同 offset 值,在组件上填充不同的 className。

    	const classes = classNames(classPrefix, className, {
    		 ...
    	     [`${classPrefix}-offset-${offset}`]: !!offset,
    	     ...
    	 }, sizeClassObj);
    
    
    .offset-cols-rules (@n, @i) {
        margin-left: (@i * 100% / @n);
    }
    
    .generate-offset-cols(@n, @i: 1) when (@i =< @n) {
        &-offset-@{i} {
            .offset-cols-rules(@n, @i);
        }
    
        .generate-offset-cols(@n, (@i + 1));
    }
    
  • pull / push 栅格向左/右移动格数 : 实现要点是将 Col 的 position 为 ‘relative’ ,通过 left / right 来左右移动 Col.

    /* pull */
    .pull-cols-rules(@n, @i) {
        position: relative;
        right: (@i * 100% / @n);
    }
    
    .generate-pull-cols(@n, @i: 1) when (@i =< @n) {
        &-pull-@{i} {
            .pull-cols-rules(@n, @i);
        }
    
        .generate-pull-cols(@n, (@i + 1));
    }
    
    /* push */
    .push-cols-rules (@n, @i) {
        position: relative;
        left: (@i * 100% / @n);
    }
    
    .generate-push-cols(@n, @i: 1) when (@i =< @n) {
        &-push-@{i} {
            .push-cols-rules(@n, @i);
        }
    
        .generate-push-cols(@n, (@i + 1));
    }
    

注:这里简单说一下 offset 和 push 的区别, offset 用 margin-left ,而 push 用 relative left,因此二者的区别就是这两个 css 属性的区别:

  • offset will force neighboring columns to move
  • push (and pull) will overlap other columns
  1. order 栅格顺序,flex 布局模式下有效 : 等价于 flex 中的 order

    Row {
    	display: flex;
    }
    
    Col {
        order: 
    }
    

更多组件

更多组件自实现系列,更多文章请参考:

从Antd 源码到自我实现之 Form表单

React 实现 Modal 思路简述

从Antd 源码到自我实现之 Menu 导航菜单

你可能感兴趣的:(Antd,前端架构,Antd,Grid,栅格,Row,Col)