React 导读(五)
在React 导读(四)中分享了组件设计最开始考虑的一些事情,不能介绍太矛盾了,其实对于设计来讲是有正反两面的分析的,就跟评论历史事件来看都是要分两面进行分析的。今天我们接着分享剩下的内容,我觉得不一定要求多,但是一定要带着思考来写,有点意识流。
首先弹框和表格是最常用的组件,下面就介绍表格吧。
表格其实是很复杂的一个组件,我们先来看下成熟的表格,然后再来看下我实现的最简单的表格组件是什么样子的。这里就看下比较流行的 antd
吧,我不会分析他的实现和代码,因为这并不利于第一次学习一个组件的封装,一个成熟的组件一般是迭代出来的,越是迭代时间长的组件就更复杂,学习前应该是知道有一个东西,然后将这个东西简化,知道它是什么,然后进行扩展会比较好一点。
这里我将表格组件的实现分为 2 类:
配置式 组合式2种没有哪一种好,各有优缺点:
配置式优点
易于使用 功能声明清晰,具有一定的整体性,代码也不分散配置式缺点
扩展、定制性其实是相对组合式较差 递归性低于组合式 组件结构非标记语言表达,在第一层阅读代码的时候主观上认为不够清晰组合式优点
组件结构标记语言实现,更清晰 扩展性较强,不容易在代码里面形成较多的 if-else 式编程组合式缺点
使用上相比配置式复杂 代码量较多这里分的比较开,但是可以是配置+组合的方式来实现组件,做一个制衡。比如:
表头无论是动态、静态构造,最后都是一个固定的配置式数据,也许你还能够自定义列展示; 数据的结构 ID 与 Columns key 进行统一的配置,其实从某种意义上数据与列也是一种约定的模式; 分页组件比较固定,就需要固定配置等等。对于 antd-table 是依赖于 rc-table 组件进行封装的,props 主要有 2 个:columns、dataSource。
配置上的基础结构,我这里用自己的描述代码:
// 一个 JSON 的结构
ColumnItem {
// String 表示 title 的数据类型是字符串
title: String;
key: String;
dataIndex: String;
// 表示 render 是一个非必填的配置,是一个函数,有 2 个值,可以返回一个 JSX 结构内容
render?: (text, record) => JSX.Element;
}
Columns = ColumnItem[];
然后使用的时候通过把配置传递进去就返回了一个你配置的表格:
<Table columns={columns} dataSource={data} />
当然 antd
的体验是不错的,还支持将 Column
组件当作子组件的方式声明,也就是我说的配置+组合的一种结合,会将可读性增强、扩展外置的优点放大。具体可以参考官方文档,这里不能介绍太多:antd-table-cn-document
随着需求的扩展,<Table />
组件需求逐渐增多,就会出现更多的依赖 props,也许会做更多的判断。虽然从需求上我们不能控制这种 if-else 的逻辑,但是我们能够通过平行组合的方式来减少单个组件的复杂度。这个是双刃剑,因为可能会有冗余。
平行组合的方式就好比有很多 if 的条件,根据组件的种类进行拆分,分成不同的组件,然后让使用者去决定用什么组件,这样一层一层的铺展使用,重的表格可能会有聚合的组件模式,这个又是另外一个问题了。
那么我这里例子里面用到的表格组件怎么弄的呢?我模仿的是 bootstrap-table 的配置方式,然后再配合 React 的一些组件特性拼起来实现的一个 Table
组件。
我这边实现的表格组件只有以下四个组件,其中 SimpleRow
组件是一个 Row
最基础的用法,满足基本的一个表格行。Table.Header
和 Table.Body
分别是对表头和表内容的装饰,主要是样式和布局上的统一。
Table
Table.Header
Table.Body
SimpleRow
具体的 Table.jsx
组件代码如下,基本等于就是一个壳:
export default class Table extends React.Component {
render() {
const {children, className} = this.props;
const classNames = cx("st-table", className);
return (
<table className={classNames}>
{/* 这里就意味着这个组件是层级 Wrap 类型的组件 */}
{children}
</table>
);
}
};
我们先看一下会如何使用表格吧。
<Table>
<Table.Header>
<th>...</th>
</Table.Header>
<Table.Body>
<tr>...</tr>
</Table.Body>
</Table>
React 组件是能够有命名空间的写法的,比如这里的 Table.<Someone>
的写法。那么我们要统一样式和布局,基本是等于就统一一下 class
就行,我们不能说让使用者自己每次去添加一些共有的代码,所以这里我们要用一个 API React.cloneElement
和 React.Children.map
,后面 map
方法可能比较好理解,类似 Array.prototype.map
,那么 React.cloneElement
的意思就跟它的表面意思一样:克隆一个元素(ReactElement),API 可以先看下官网 cloneElementApi和mapApi。
先看看一下 Table.Body
的代码吧:
Table.Body = ({ children }) => {
// 1. 先遍历孩子元素
const cloneChild = React.Children.map(children, (child, i) => {
if(!child) {
return null;
}
// 2. 要更新的孩子组件的属性
const wrapProps = {
key: `st-table-row_${i}`,
className: cx("st-table-row", child.props.className)
};
// 3. 返回一个新的 clone 的 React 元素,这里可以思考一下为什么要 clone
return React.cloneElement(child, wrapProps);
});
// 4. 最后返回一个 tbody 原始的容器
return (<tbody className="st-table-tbody">{cloneChild}</tbody>);
};
这里的代码可以看 github 上完整的源码:TableComponent
那么我们组件的结构通过组合的写法构造好了,那么我们的 Columns
在哪儿去声明呢?我们这里也是通过 js 数组对象的方式来声明,结构如下:
const table = [
{
field: "id",
title: "ID",
width: 50
},
{
field: "sex",
title: "性别",
format(value, row, index) {
if(!value) {
return "保密";
}
if(value === Gender.Girl) {
return "女";
} else if(value === Gender.Boy) {
return "男";
}
}
},
...
];
这里的配置怎么和表格的列连接起来呢?我这里将业务表格组件的目录划分成了这样:
├── dataTable
│ ├── config.js // 表格的配置
│ ├── dataTable.css // 表格的样式
│ └── index.jsx // 表格组件
// 初始化 header
const headers = table.map((row, index) => {
let width = row.width;
return <th key={`header-${index}`} width={width}>{row.title}</th>;
});
// 多一个操作列,这个操作列我这里是没有 antd 那么智能,识别到差 1 列的时候就成为了操作什么的
headers.push(<th width="160" key="opts">操作</th>);
// 初始化每行的内容
const rows = (this.props.data || []).map((item, i) => {
return (
<SimpleRow key={item.id} tableConfig={table} row={item} index={i}>
{/* SimpleRow 通过传入的 TableConfig 和 Row 数据,初始化好了基本的行元素 */}
{/* SimpleRow 也是一个 this.props.children 的结构 */}
<td key={`opt-${i}`}>
<Button color="blue" onClick={() => this.props.onEdit(item)}>编辑</Button>
<Button color="red" onClick={() => this.props.onDelete(item)}>删除</Button>
</td>
</SimpleRow>
);
});
这样回到最开始那个使用表格的例子,将 header
, rows
都添加进入即可,最后整个表格 Row 的效果就是这样子,SimpleRow
的实现代码依然能在业务表格 Github 文件中找到:
这种写组件的方法特别费代码,所以是否可以考虑封装成一个业务通用的表格组件呢?这肯定是可以的,将变化的部分通过 props
传入即可,这个工作可以自己动手试试,在我的源码基础上进行修改。还有就是 tableConfig 里边的对象属性,你是能按自己需求进行添加的,可以在看看源码中的配置。
最后表格实现的效果,虽然没有排序、固定表头,但是用来展示信息是已经完善了,比如点击表头进行排序这个可以动手试试,在我组件的基础上应该比较好添加:
这里表格组件就实现了,你肯定会想,为什么没有 antd 那么方便实用和强大呢?
(1) antd-table 和这里介绍的 Table 组件层级是不同的,更接近的应该是 rc-table; (2) 这只是针对最简单的表格使用场景进行设计的,按的是自由编写修改,并不是一个复杂场景的表格设计。
今天先写到这里吧~11点半了,休息一会儿。
文章来源:
Author:小撸
link:http://www.60sky.com/post/react-intro-5.html