Grid 布局的栅格化系统,我们是基于行(row)和列(col)来定义信息区块的外部框架,以保证页面的每个区域能够稳健地排布起来。下面简单介绍一下它的工作原理:
通过row在水平方向建立一组column(简写col)
你的内容应当放置于col内,并且,只有col可以作为row的直接元素
栅格系统中的列是指1到24的值来表示其跨越的范围。例如,三个等宽的列可以使用.col-8来创建
如果一个row中的col总和超过 24,那么多余的col会作为一个整体另起一行排列
此外, Grid 还支持 Flex 布局。
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;
}
先来看一下代码实现:
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 上层组件,主要提供以下功能点:
先看一下 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 的实现方式:
// 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 属性的区别:
order 栅格顺序,flex 布局模式下有效 : 等价于 flex 中的 order
Row {
display: flex;
}
Col {
order:
}
更多组件自实现系列,更多文章请参考:
从Antd 源码到自我实现之 Form表单
React 实现 Modal 思路简述
从Antd 源码到自我实现之 Menu 导航菜单