用react 构建电子表格(2)---选择单元格上半节
此文转载自:https://blog.csdn.net/wh_xia_jun/article/details/109916408
表格,首先要解决选择单元格的问题,选择相应单元格后,才能进一步修改样式等。
"我们用 state 来实现所谓“记忆”的功能"。
点击单元格的时候,必须给它加上某种记忆,点击放在table上,还是放在单元格上呢?这是个问题。
ps:样式也是一种数据,必须把相关类加上。
选择单元格,有2个要素:
- 记忆选择的单元格
- 表现选择的单元格
单元格的选择,当然靠点击事件,或者鼠标点击+按键盘特定键来解决。
react的点击事件,目前我理解就是js点击事件,语法略有差异而已。React 事件处理
记忆点击的单元格,涉及数据的存储。一个是存在对象本身,一个是向上级组件传播。
当前,每个 cell 组件都维护了选择的状态。我们可以把所有 cell 的是否被选择放在一个地方。
你可能会想,我们也可以在table 组件中收集每个格子组件 中的 state。虽然技术上来讲是可以实现的,但是代码如此编写会让人很难理解,并且我们以后想要维护重构时也会非常困难。所以,最好的解决方式是直接将所有的 state 状态数据存储在 table父组件当中。之后 table 组件可以将这些数据通过 props 传递给各个 cell子组件。
先在table 当中搞个数组,用于记忆每个是否选中状态。
这里是搞个对象封装它们呢,还是直接原生数据类型呢 纠结,先搞个原生数据类型数组。需要的时候再扩展为对象,把是否被选中作为这个对象的属性。
constructor(props) { super(props);
let r = this.props.rowNum; //行数
let c = this.props.colNum; //列数
let cellSelectStates=[]; //单元格是否被选中
for(let i=0;i<r;i++){
cellSelectStates[i]=new Array();
for(let j=0;j<c;j++){
cellSelectStates[i][j]=false;
}
}
this.state = {
cellSelectStates:cellSelectStates ,//单元格是否被选中
};
}
然后逐级向下传递:
getRows(colNum, rowNo,rowIndex) { return <CommRow colNum={colNum}
rowNo={rowNo}
key={"dataRow" + rowNo + "_" + colNum}
isSelect_cells={this.state.cellSelectStates[rowIndex]}
/>
}
export default class CommRow extends Component { constructor(props) {
super(props);
this.state = {
//一维数组 标记一行内单元格选中情况 由prop传入
cellSelectStates_row:this.props.isSelect_cells ,//单元格是否被选中,
};
}
.....
}
行向单元格传递以此类推。
点击事件包括:单元格点击、行点击、表点击、页面点击
选择单元格,我们当然首先考虑单元格的点击。
表格编辑一般会有区域选中的问题,我们先只考虑选中一个单元格的情况。
先在单元格上加点击事件。
接下来,我们要修改一下 Cell的点击事件监听函数。Table 组件当前记忆了单元格选择情况。我们需要想办法让Cell 去更新 Table 的 state。由于 state 对于每个组件来说是私有的,因此我们不能直接通过 Cell来更新 Table 的 state。
相反,从 Table 组件向 Row 组件传递一个函数,Row组件向Cell组件传递一个函数,当Cell 被点击的时候,这个函数就会被调用。接着,我们将 Row组件的getCommCell 方法改写为如下效果:
getCommCell(rowNo,colNo,isSelected) { return <CommCell
rowNo={rowNo}
colNo={colNo}
key={"CommCell"+rowNo+"_"+colNo}
isSelected={isSelected}
onClick={() => this.handleClick(isSelected)}
/>
}
现在我们从 Row组件向 Cell 组件中传递props 参数中:有 onClick
。onClick
prop 是一个 Cell 组件点击事件监听函数。接下来,我们需要修改 Cell的代码:
进行上述修改之后,代码会变成下面这样:
render() { return (
<td className="ttd"
onClick={() => this.props.onClick()}
>
</td>
)
}
一个 cell被点击时,row 提供的 onClick
函数就会触发。我们回顾一下这是怎么实现的:
- 向 DOM 内置元素
<td>
/cell添加onClick
prop,让 React 开启对点击事件的监听。 - 当 td被点击时,React 会调用 cell组件的
render()
方法中的onClick
事件处理函数。 - 事件处理函数触发了传入其中的
this.props.onClick()
方法。这个方法是由 row传递给cell 的。 - 由于 row把
onClick={() => this.handleClick(i)}
传递给了 cell,所以当 cell中的事件处理函数触发时,其实就是触发的 row当中的this.handleClick(i)
方法。 - 现在我们还尚未定义
handleClick()
方法,所以代码还不能正常工作。如果此时点击 td,你会在屏幕上看到红色的错误提示,提示内容为:“this.handleClick is not a function”
这时候我们点击 td的时候,浏览器会报错,因为我们还没有给row定义 handleClick
方法。我们现在来向 row 里添加 handleClick
方法:
export default class CommRow extends Component { constructor(props) {
super(props);
this.state = {
//一维数组 标记一行内单元格选中情况 由prop传入
cellSelectStates_row:this.props.isSelect_cells ,//单元格是否被选中,
};
}
handleClick(selectStatus) {
alert('row')
}
....
}
这里,我们应该进一步调用table对象的方法,table当中有个函数,专门处理选择单元格的事情:
//处理单元格选择 handleCellSelect = (i,j) => {
alert(i);
//复制一个数组
let cellSelectStates_s=this.state.cellSelectStates;
let cellSelectStates_temp=[];
for(let i=0;i<cellSelectStates_s.length;i++){
let [...cellSelectStates_r]=cellSelectStates_s[i];
cellSelectStates_temp.push(cellSelectStates_r);
}
// cellSelectStates_temp[0][0]=true;
//this.setState({cellSelectStates: cellSelectStates_temp});
}
这个方法,需要通过prop向row对象传递。
getRows(colNum, rowNo,rowIndex) { return <CommRow colNum={colNum}
rowNo={rowNo}
key={"dataRow" + rowNo + "_" + colNum}
isSelect_cells={this.state.cellSelectStates[rowIndex]}
handleCellSelect={(i,j)=> this.handleCellSelect(i,j)}
/>
}
row对象当中通过prop 获取这个函数
handleClick=(rowNo,colNo)=> { alert('row'+rowNo+"_"+colNo);
this.props.handleCellSelect(rowNo,colNo);
}
row获取到父对象函数后,接力想cell传递函数:
getCommCell(rowNo,colNo,isSelected) { return <CommCell
rowNo={rowNo}
colNo={colNo}
key={"CommCell"+rowNo+"_"+colNo}
isSelected={isSelected}
onClick={() => this.handleClick(rowNo,colNo)}
/>
}
cell 当中直接使用:
//Cell.jsimport React, { Component } from 'react';
export default class CommCell extends Component {
constructor(props) {
super(props);
this.state = {
// 标记单元格选中情况 由prop传入
isSelected:this.props.isSelected ,//单元格是否被选中,
};
}
render() {
return (
<td className="ttd"
onClick={() => this.props.onClick()}
>
</td>
)
}
}
一个函数,从Table出发,传到到row,由row继续传递到cell.
点击单元格后,怎么表现呢 目前我觉得有2种方式:
1、搞个边框线,把选择的单元格框起来,这样做的问题好处是 对表格没啥侵入性,缺陷是略复杂,因为要获取选择的单元个的位置信息(坐标信息),再搞线的定位。
2、修改选择单元格的样式 如修改相关单元格的边线样式、或者修改选择单元格的背景色
修改单元格边线样式?其实要找出选择区域的边界上个各个单元格。
修改选中单元格的背景色,似乎更简单,通过色块即可标记处选择区域。
下节即介绍选择单元格显示的实现。
ps:二维数组copy,不解释,看代码
二维数组copy 注意不能用slicevar a = [[1,2,3],[2,3,4]];
console.log(a);
console.log("========================");
var b=a.slice();
let re=[];
for(let i=0;i<a.length;i++){
console.log("i"+i);
let [...arr1]=a[i];
console.log(arr1);
re.push(arr1);
}
console.log("--------------------");
console.log(re);
re[1][1]=re[1][1]*re[1][1];
console.log("**************************************");
console.log(re);
console.log("#################################");
console.log(a);
以上是 用react 构建电子表格(2)---选择单元格上半节 的全部内容, 来源链接: utcz.com/z/383938.html