zoukankan      html  css  js  c++  java
  • 用react 构建电子表格(2)选择单元格上半节

    此文转载自:https://blog.csdn.net/wh_xia_jun/article/details/109916408

    表格,首先要解决选择单元格的问题,选择相应单元格后,才能进一步修改样式等。

    "我们用 state 来实现所谓“记忆”的功能"。

    点击单元格的时候,必须给它加上某种记忆,点击放在table上,还是放在单元格上呢?这是个问题。

    ps:样式也是一种数据,必须把相关类加上。

    选择单元格,有2个要素:

    1. 记忆选择的单元格
    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 参数中:有 onClickonClick prop 是一个 Cell 组件点击事件监听函数。接下来,我们需要修改 Cell的代码:

    进行上述修改之后,代码会变成下面这样:

        render() {
           return (
                <td  className="ttd"
                     onClick={() => this.props.onClick()}
                >
         </td>
            )
        }

    一个 cell被点击时,row 提供的 onClick 函数就会触发。我们回顾一下这是怎么实现的:

    1. 向 DOM 内置元素 <td> /cell添加 onClick prop,让 React 开启对点击事件的监听。
    2. 当 td被点击时,React 会调用 cell组件的 render() 方法中的 onClick 事件处理函数。
    3. 事件处理函数触发了传入其中的 this.props.onClick() 方法。这个方法是由 row传递给cell 的。
    4. 由于 row把 onClick={() => this.handleClick(i)} 传递给了 cell,所以当 cell中的事件处理函数触发时,其实就是触发的 row当中的 this.handleClick(i) 方法。
    5. 现在我们还尚未定义 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.js
    import 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   注意不能用slice
    var 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);
    
    
  • 相关阅读:
    NOI 2016 区间 解题报告
    有关莫队
    [JSOI2008]最大数 线段树解法
    HDU P3341 Lost's revenge 题解+数据生成器
    BZOJ P1212 [HNOI2004] L语言
    洛谷P3168 [CQOI2015]任务查询系统
    普通平衡树Tyvj1728、luogu P3369 (splay)
    洛谷P3384 树链剖分
    BZOJ P2157 旅游
    【算法导论】第6章,堆排序
  • 原文地址:https://www.cnblogs.com/phyger/p/14036046.html
Copyright © 2011-2022 走看看