从零开始的 React 组件开发之路 (一):表格篇
React 下的表格狂想曲0. 前言欢迎大家阅读「从零开始的 React 组件开发之路」系列第一篇,表格篇。本系列的特色是从 需求分析、API 设计和代码设计 三个递进的过程中,由简到繁地开发一个 React 组件,并在讲解过程中穿插一些 React 组件开发的技巧和心得。 为什么从表格开始呢?在企业系统中,表格是最常见但功能需求最丰富的组件之一,同时也是基于 React 数据驱动的思想受益最多的组件之一,十分具有代表性。这篇文章也是近期南京谷歌开发者大会前端专场的分享总结。UXCore table 组件Demo也可以和本文行文思路相契合,可做参考。
1. 一个简单 React 表格的构造1.1 需求分析
1.2 API 设计
1.3 代码设计
图:最初的 Table 结构,详细的分层为后续的功能扩展做好准备。 2. 加入更多的内置功能
2.1 需求分析
2.2 API 设计
// table 配置,需求对应的模块对应了他的配置在整个配置中的位置
{
columns: [ // HEAD/ROW 相关
{
order: true,161)">// 是否展示排序按钮
hidden: false,161)">// 是否隐藏,行筛选需要
}
],onOrder: function (activeColumn,order) { // 排序时的回调
doOrder(activeColumn,order)
},actionBar: { // 常用操作条
"打印": function() {doPrint()},},showSeach: true,161)">// 是否显示搜索过滤,为什么不直接用下面的,这里也是设计上的一个优化点
onSearch: function(keyword) { doSearch(keyword) },161)">// 搜索时的回调
showPager: true,161)">// 是否显示分页
onPagerChange: function(current,pageSize) {},161)">// 分页改变时的回调
rowSelection: { // 行选择相关
onSelect: function(isSelected,currentRow,selectedRows) {
doSelect()
}
}
}
// data 结构
{
data: [{
city: 'xxx',__selected__: true,161)">// 行选择相关,用以标记该行是否被选中,用前后的 __ 来做特殊标记,另一方面也尽可能避免与用户的字段重复
}],currentPage: 1,161)">// 当前页数
totalCount: 50,161)">// 总条数
}
2.3 代码设计结构图
图:扩展后的 Table 结构 内部数据的处理
何时该用 state?何时该用 props?UI=fn(state,props),人们常说 React 组件是一个状态机,但我们应该清楚的是他是由 state 和 props 构成的双状态机; props 和 state 的改变都会触发组件的重新渲染,那么我们使用它们的时机分别是什么呢?由于 state 是组件自身维护的,并不与他的父级组件进行沟通,进而也无法与他的兄弟组件进行沟通,因此我们应该尽量只在页面的根节点组件或者复杂组件的根节点组件使用 state,而在其他情况下尽量只使用 props,这可以增强整个 React 项目的可预知性和可控性。 但凡事不是绝对的,全都使用 Props 固然可以使组件可维护性变强,但全部交给用户来操作会使用户的使用成本大大提高,利用 state,我们可以让组件自己维护一些状态,从而减轻用户使用的负担。 我们举个简单的例子 {/* 受控模式 */}
<input value="a" onChange={ function() {doChange()} } />
{/* 非受控模式 */}
<input onChange={ function() {doChange()} } />
value 配置时,input 的值由 value 控制,value 没有配置时,input 的值由自己控制,如果把 <input /> 看做一个组件,那么此时可以认为 input 此时有一个 state 是 value。显然,无 value 状态下的配置更少,降低了使用的成本,我们在做组件时也可以参考这种模式。 例如在我们希望为用户提供 class Table extends React.Component {
constructor(props) {
super(props);
this.data = deepcopy(props.data);
this.state = {
data: this.data,};
}
/** * 在 data 发生改变时,更改对应的 state 值。 */
componentWillReceiveProps(nextProps,nextState) {
if (!deepEqual(nextProps.data,this.data) {
this.data = deepcopy(nextProps.data);
this.setState({
data: 这里涉及的一个很重要的点,就是如何处理一个复杂类型数据的 prop 作为 state。因为 JS 对象传地址的特性,如果我们直接对比
3.3 代码设计
this.fetchParams = deepcopy(props.fetchParams);
/** * 获取数据的方法 */
fetchData(props,from) {
props = props || this.props;
const otherParams = process(this.state);
ajax(props.url,0)">this.fetchParams,otherParams,from);
}
/** * 搜索时的回调 */
handleSearch(key) {
if (this.props.url) {
this.setState({
searchKey: key,() => {
this.fetchData();
});
} else {
this.props.onSearch(key);
}
}
componentDidMount() {
this.fetchData();
}
}
componentWillReceiveProps(nextProps,0)">let newState = {};
this.data = deepcopy(nextProps.data);
newState['data'] = this.data;
}
if (!deepEqual(nextProps.fetchParams,0)">this.fetchParams)) {
this.fetchParams = deepcopy(nextProps.fetchParams);
this.fetchData();
}
if (nextProps.url !== this.fetchData(nextProps);
}
if (Object.keys(newState) !== 0) {
this.setState(newState);
}
}
}
4. 行内编辑4.1 需求分析通过双击或者点击编辑按钮,实现行内可编辑状态的切换。如果只是变成普通的文本框那就太 low 了,有追求的我们希望每个列根据数据类型可以有不同的编辑形式。既然是可编辑的,那么关于表单的一套东西都适用,他要可以验证,可以重置,也可以联动。 4.2 API 设计// table 配置,需求对应的模块对应了他的配置在整个配置中的位置,显然行内编辑是和列相关的
{
columns: [ // HEAD/ROW 相关
{
dataKey: 'cityName',161)">// 展示时操作的变量
editKey: 'cityValue',161)">// 编辑时操作的变量
customField: SelectField,161)">// 编辑状态的类型
config: {},161)">// 编辑状态的一些配置
renderChildren: function() {
return [
{id: 'bj',152)">'北京'},{id: 'hz',152)">'杭州'}].map((item) => {
Option key={item.id}>{item.name}</Option> }); },rules: function(value) { // 校验相关 return true; } } ],onChange: function(result) { doSth(result); // result 包括 {data: 表格的所有数据,changedData: 变动行的数据,dataKey: xxx,editKey: xxx,pass: 正在编辑的域是否通过校验} } }
// data 结构
{
data: [{
cityName: 'yyy',__mode__: "edit",161)">// 用来区分当前行的状态
}],161)">// 总条数
}
4.3 代码设计
图:行内编辑模式下的表格架构
5. 总结
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |