V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
simonlify
V2EX  ›  React

关于 ReactJS+ES6 组件开发 重复渲染问题

  •  
  •   simonlify · 2016-08-24 12:16:47 +08:00 · 7870 次点击
    这是一个创建于 3010 天前的主题,其中的信息可能已经有所发展或是发生改变。

    大家好: 我的前端项目是 React+es6+redux+webpack 结构

    功能很简单,获取数据,渲染列表,问题在于,我把列表渲染完之后,需要调一个 bootgrid 的 jQuery 库,去生成分页和自定义操作的界面操作和效果,那么,在初次加载的时候,没有问题,但是在做 select 筛选的时候, componentDidMount 里面的没办法再次调用 bootgrid 的 jQuery 库,这样就导致了重新渲染的列表没办法展示其效果。我琢磨了很长时间,没找到解决办法。
    请问各位,是否有办法解决这个问题???  在线等!
    
    简单代码如下:
    class ClientsHistory extends Component {
    constructor(props) {
        super(props);
        this.state = {list: []};
    }
    componentDidMount(){
        let that = this;
        $("#tb-grid-data").bootgrid();
        let grid = $("#tb-grid-devicelist").bootgrid({
            formatters:{
                "operation":function(column , row){ 
                    return 'aaa';
                }
            }
        });
    }
    changeSelect(sel_val){
        this.setState({devicesn:sel_val});
        // $("#tb-grid-devicelist").bootgrid('reload');
    
    }
    render(){
        let devicelist = GetLocalData(device_data_key);
        var rows = [];
        if(devicelist != null){
            var sn = this.state.devicesn;
            devicelist.forEach(function(result, index){
                if(typeof sn != 'undefined' && sn != ''){
                    if(result.sn == sn){
                        rows.push(
                            <ClientRows rowdata={result} key={index} />
                        );
                    }
                }else{
                    rows.push(
                        <ClientRows rowdata={result} key={index} />
                    );
                }
            });
        }
        return(
            <div className="card z-depth-1">
                <div className="card-header">
                    <div className="div-tips-left">
                        <Select1 changeselect={this.changeSelect.bind(this)}/> 
                    </div>
                </div>
                <div className="table-responsive">
                    <table id="tb-grid-devicelist" className="table table-striped">
                        <thead>
                            <tr>
                                <th data-column-id="sn">aa</th>
                                <th data-column-id="uptime">bb</th> 
                            </tr>
                        </thead>
                         <tbody>
                            {rows}
                        </tbody>
                    </table>
                </div>
            </div>
        );
    }
    

    }

    27 条回复    2016-09-03 18:10:43 +08:00
    bdbai
        1
    bdbai  
       2016-08-24 13:36:20 +08:00 via Android
    http://reactjs.cn/react/docs/component-specs.html

    你可以自己把 bootgrid 封装成一个子组件,参数从 props 传进去。这样一旦父组件条件变化,子组件就能重新渲染。
    simonlify
        2
    simonlify  
    OP
       2016-08-24 14:15:51 +08:00
    @bdbai 你说的这种方法我试过了,好像不行的,父组件中 select 选择,触发事件,生成筛选参数,这时候,我只能把参数存到 state 中,这时候就会重新渲染 html 结构,在这之后才能调用 bootgrid 重新生成分页和操作标签,
    问题在于,一旦调用 setState 后,最后的操作肯定是 render() 之后就不会有任何操作了,而我希望, render()之后,在调用 bootgrid ,刷新页面效果,不知道我说清楚了没?
    bdbai
        3
    bdbai  
       2016-08-24 16:10:21 +08:00
    @simonlify 子组件的 componentDidMount 和 componentDidUpdate 都要调用 bootgrid 。我简单改了一下子组件,你看这样如何。

    https://gist.github.com/bdbai/2a195e83fc0fce13d2f9a6aa4d9dac5f
    ziki
        4
    ziki  
       2016-08-24 16:37:43 +08:00
    componentDidMount 是只有首次渲染才执行的,那你试下 componentDidUpdate
    Axighi
        5
    Axighi  
       2016-08-24 16:45:21 +08:00
    componentWillReceiveProps
    simonlify
        6
    simonlify  
    OP
       2016-08-24 17:03:15 +08:00
    @bdbai 你的代码,我没看到,不过我加了 componentDidUpdate ,不管用啊

    @ziki 我加过了,没用

    @Axighi componentWillReceiveProps 这个我也加过,没用,
    我贴一下,我修改过的代码 , 我打过 alert , b 和 c 都不会执行,

    class Exam extends Component{
    .......

    componentDidMount(){
    let grid = $("#tb-grid-devicelist").bootgrid({
    formatters:{
    "operation":function(column , row){
    return 'aaa';
    }
    }
    }).on("loaded.rs.jquery.bootgrid", function(){
    grid.find(".devicelist-operation").on("change", function(e){
    alert("You select on row: " + $(this).data("row-sn"));
    });
    });
    }
    componentDidUpdate(){
    // this.refs.exam.onselect();
    alert('b')
    let grid = $("#tb-grid-devicelist").bootgrid({
    formatters:{
    "operation":function(column , row){
    return 'aaa';
    }
    }
    }).on("loaded.rs.jquery.bootgrid", function(){
    grid.find(".devicelist-operation").on("change", function(e){
    alert("You select on row: " + $(this).data("row-sn"));
    });
    });
    }
    componentWillReceiveProps(){
    alert('c')
    let grid = $("#tb-grid-devicelist").bootgrid({
    formatters:{
    "operation":function(column , row){
    return 'aaa';
    }
    }
    }).on("loaded.rs.jquery.bootgrid", function(){
    grid.find(".devicelist-operation").on("change", function(e){
    alert("You select on row: " + $(this).data("row-sn"));
    });
    });
    }
    changeSelect(obj, sel_val){
    alert('a -- '+ sel_val)
    this.setState({devicesn:sel_val});
    // $("#tb-grid-devicelist").bootgrid('reload');

    }
    render(){
    return(
    ..........
    );
    }

    }
    Axighi
        7
    Axighi  
       2016-08-24 17:13:37 +08:00
    在 changeSelect 里的 setState 中加回调,应该可以。
    Axighi
        8
    Axighi  
       2016-08-24 17:15:11 +08:00
    setState({}, () => { //do something})
    bdbai
        9
    bdbai  
       2016-08-24 17:17:42 +08:00
    @simonlify 翻墙乃程序员必备技能,再说 Gist 也没被墙。
    http://paste.ubuntu.com/23084405/
    simonlify
        10
    simonlify  
    OP
       2016-08-24 17:56:15 +08:00
    @bdbai 亲,我的 vpn 账号可是付费的,不要怀疑我不会翻墙!!刚才确实没打开!这个连接看到了
    simonlify
        11
    simonlify  
    OP
       2016-08-24 18:14:09 +08:00
    @bdbai 看过你的代码了,有些疑问,我的 render() 中有一个 select 的过滤条件:代码如下
    <div className="card-header">
    <div className="div-tips-left">
    <Select1 changeselect={this.changeSelect.bind(this)}/>
    </div>
    <div className="div-clear"></div>
    </div>

    我一般是触发 select 的时候,获取过滤条件,然后在 changeSelect()里面去 setState()条件,然后 render
    你的代码中有个 shouldComponentUpdate 这个事件怎么触发??
    simonlify
        12
    simonlify  
    OP
       2016-08-24 18:15:23 +08:00
    @Axighi 我有这样写,但是没有效果,选择过滤条件,貌似没办法触发回调??
    代码如下:
    this.setState({devicesn:sel_val}, ()=>{
    alert('e')
    });
    serco
        13
    serco  
       2016-08-24 18:18:29 +08:00
    @simonlify 你一定哪里写错了,如果你的 changeSelect 确实执行了,肯定也会再执行到 componentDidUpdate. 你暂时可以不去管 shouldComponentUpdate ,那个只是控制 state 或者 props 改变时是否更新,默认是 true
    bdbai
        14
    bdbai  
       2016-08-24 19:34:02 +08:00 via Android
    @simonlify 也许污染的 DNS 被缓存了。

    子组件只关心需要显示哪些项目,由父组件从 props 传进去即可。过滤归别的组件( Select1 )管。

    一旦父组件发现过滤条件有变,就会把新的筛选出的内容传递给子组件(调用 shouldComponentUpdate ),使子组件重新渲染(调用 componentDidUpdate )。如果 shouldComponentUpdate 发现实际过滤出来的东西没变,就直接返回 false 省去一次多余的渲染。
    simonlify
        15
    simonlify  
    OP
       2016-08-24 19:40:13 +08:00
    @serco 这段代码我反复检查了很多遍 ,不会有什么语法错误
    changeSelect 如下:
    changeSelect(obj, sel_val){
    alert('a -- '+ sel_val)
    this.setState({devicesn:sel_val});
    }
    这段代码执行后,下面就是 render() 然后就没有了,不会走到 componentDidUpdate 中去,我很郁闷!!!
    ianva
        16
    ianva  
       2016-08-24 20:38:57 +08:00
    LZ 这个 render 逻辑写的真是,就这么简单的逻辑非要绕成看不懂

    ```
    devicelist && devicelist.map((result,index)=>
    result.sn === sn ? <ClientRows rowdata={result} key={index} /> : null)
    ```

    之前设置个默认的 getInitialState devicesn 设 '',就完事了绕成这样
    simonlify
        17
    simonlify  
    OP
       2016-08-24 21:04:36 +08:00
    @bdbai 根据你的建议,我重新改了一下组件结构,父组件包括 Select 和 DeviceList 两个子组件
    select 的值 以<DeviceList devicesn={ devicesn } /> 这种方式传入
    如果在父组件中用 setState() 确实会重新渲染子组件,问题在于,不管用什么方式,都不会重新调用 bootgrid
    也就是说,再次渲染子组件时, componentDidUpdate 是不会执行到的,
    难道我漏掉了什么???
    simonlify
        18
    simonlify  
    OP
       2016-08-24 21:06:04 +08:00
    @ianva React 方面是新手,见谅!!
    bdbai
        19
    bdbai  
       2016-08-24 21:22:14 +08:00
    @simonlify 把 shouldComponentUpdate 去掉试试?

    你有完整的代码和 mock 数据嘛,我来跑跑看。
    arslion
        20
    arslion  
       2016-08-24 21:22:40 +08:00
    是有多喜欢这个 bootgrid …
    分页刷新查询选择吧啦吧啦,到底有多难写啊。你貌似已经在这个问题上纠结了一天多了,不如就此打住,延一天的时间自己写组件

    对了,不是很懂这种感叹号的用法,一种微咆哮的感觉 ;)
    simonlify
        21
    simonlify  
    OP
       2016-08-24 21:53:23 +08:00
    @arslion 这个问题确实是让我纠结半天多了,我之所以一直纠结这个问题,不是因为我没有别的解决办法,别的办法随便想想也有好几种。
    只是本人刚做这种前端组件框架几个星期而已,很多原理方面的知识都不太清楚,如果碰到了这个问题,即使纠结这么长时间,也是搞清楚了蛮多别的问题,也算是避免以后再次踩类似这样的坑,不算没有收获
    simonlify
        22
    simonlify  
    OP
       2016-08-24 22:01:02 +08:00
    @bdbai 还是不麻烦你啦, shouldComponentUpdate 去掉我也试过,没有效果,也许是我还理解的不够深刻吧,不过还是搞明白了很多问题
    这个问题呢,我想来想去,没必要再纠结下去了,我换了一种方式去解决了
    大概的思路就是, select 获取过滤条件后,不用 setState 去重新渲染子组件列表,我从 bootgrid.js 源码里面下手,修改了一些源码,不管怎么说,这个刷选,我算是搞定了
    不过这个问题的本质,确实是没有解决的,我现在项目时间比较急,只能等有空了再来研究这个问题了
    谢谢各位耐心的解答,非常感谢!
    bdbai
        23
    bdbai  
       2016-08-24 22:08:21 +08:00 via Android
    @simonlify 最漂亮的方案是自己把 bootgrid.js 用 React 组件重写,然后开源...
    ianva
        24
    ianva  
       2016-08-25 00:48:11 +08:00
    @simonlify 不是库的使用问题,是编程本身对于圈复杂度的控制
    simonlify
        25
    simonlify  
    OP
       2016-08-25 09:59:15 +08:00
    @bdbai 哈哈,这个...这个... 目前暂不考虑, 分析 bootgrid 就够麻烦的了,还要组件重写,关键是我写 ReactJS ,不到一个月,完全没勇气完成这项壮举,还是等后来者吧。
    simonguo
        26
    simonguo  
       2016-09-03 11:37:03 +08:00
    没有你这么做的,既然选择了用 react ,为什么还要用 bootgrid.js 去渲染 table ,建议你不要这样做。 推荐一个 react table 组件 http://rsuite.github.io/rsuite-table/
    simonlify
        27
    simonlify  
    OP
       2016-09-03 18:10:43 +08:00
    @simonguo 感谢,这个我也是后来才知道的,之所以用 bootgrid ,是因为这是我用 React 做的第一个项目,很多方面没有规范化,而且这个项目已经势成骑虎,不好在中途换了,也算是累积经验吧,下一个项目肯定不会再出现这种情况了,再次感谢!
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2139 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 16:13 · PVG 00:13 · LAX 08:13 · JFK 11:13
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.