React与d3交互效果实现所思

前言

最近使用d3 + React制作一个可视化系统。d3与React的思想有些违背,d3直接操作DOM,React有一层Fiber Tree(Virtual DOM)再通过render函数渲染。

但是两者通过一些技巧也可以相结合,方式主要有两种:

  1. 在React合适的生命周期,d3直接操作DOM进行绘制,即d3渲染UI
  2. React利用d3的布局算法,在render中设置svg的标签位置,即React渲染UI。业界也有很多以这种形式渲染的可视化react库,如react-vis,但是个人感觉多数并不是特别活跃,同时也缺乏扩展性。

各种方式有各种方式的好处,在此不做赘述

此系统需要实现交互效果,所以使用d3渲染UI

问题

系统开发过程中,树图需要实现交互效果:鼠标mousemove,悬浮弹窗显示具体信息。

这个悬浮窗设置了position:fixed用于定位,定位具体位置既可以给d3做,也可以给react做。

为了提升开发效率(赶工期)此次实现中我把他交给了react,为树图添加mousemove监听,获取对应位置,setState传值给div控制悬浮窗。因此后面的问题及思考也是针对react。

但也随之带来了两个问题:

  1. 未处理好组件层级关系,setStaet导致componentDidUpdate中的d3代码高频重绘,性能很差
  2. mousemove下,setState多次调用效果合并,导致悬浮窗位移是闪动的,用户体验差

以下针对两个问题记录下实践中的思考

解决

1.未处理好组件层级关系,setStaet导致d3高频重绘,性能很差

一开始为切分组件,结构如下:

<DetailTreeMap />
constructor(props) {
    super(props);
    this.staet = {
        left:...
        top:...
        //...
    }
}
componentDidUpdate(){
    //...
    //...
    .on("mousemove",()=>{
        //...
        this.setSate({
        left:X,
        top:Y
        })
    })
}
render(){
    //......
    return(<div>
        {/* d3绘图区域 */}
        <svg></svg>
        
        {/* 悬浮窗div */}
        <div style={{left:state.left, top:state.top}}>
            //......
        </div>
    </div>)
}

每次mousemove时都会调用setState,后续触发一系列的生命周期函数,而我们的绘图代码放在componentDidUpdate之中,导致了高频的d3布局计算、直接操作svg与dom节点等等,性能非常差。

这里我想到两种方法解决这个问题:

  1. 绘图代码添加更多的条件判断
  2. 将悬浮窗div外提为单独的组件不放置在<DetailTreeMap>中,设置<DetailTreeMap>为PureComponent,以此减少render函数及ComponentDidUpdate的触发

以上两种方法的最终目的都是减少ComponentDidUpdate中d3绘图代码的并不必要执行

本次实现中,由于ComponentDidUpdate中已经存在较多的条件判断,所以我选择了第二种方法。如此,组件结构如下

<CityDetail>
    <DetailTreeMap />
    <TreeMapWindow left={state.left} top={state.top}/>

通过父级组件CityDetail设置state及props传递值。

2.mousemove下,setState多次调用效果合并,导致悬浮窗位移是闪动的,用户体验差

这个问题并不是性能问题,而是react setState自身的优化所导致的。

但我们的最终目的就是提升用户体验,同时不会损耗太多的性能。

解决方法非常简单,根本原因是间断性的div位移,我们直接给这些间断性的位移加上动画就好了

.detail-window {
    position: fixed;
    width: 200px;
    height: 100px;
    transition: top 200ms, left 200ms;
    background: rgba(0, 0, 0, 0.6);
    padding: 10px;
}

如此,尽管触发mousemove也不会有悬浮框跳动的问题。

思考及总结

本次案例中实现悬浮窗跟随的方式还有很多,比如使用d3实现,使用React渲染UI等等,但是由于时间关系还没来得及一一尝试。

此次案例中给自己带来的更多思考是React与d3一起使用时,d3渲染UI的注意事项,如何防止不必要的重绘,如何提升开发效率而又不是用户体验……

私以为对组件更加合适的层级划分及componentDidUpdate中的绘制代码添加条件可一定程度上解决问题。

见识可能还甚少,欢迎大佬们指教交流

参考资料

setState:这个API设计到底怎么样

https://juejin.im/post/5dee2ff65188251264166064

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论