Skip to content

Instantly share code, notes, and snippets.

@LinZap
Last active August 18, 2016 14:23
Show Gist options
  • Save LinZap/fe665ce5d74125d1060931212e4400d3 to your computer and use it in GitHub Desktop.
Save LinZap/fe665ce5d74125d1060931212e4400d3 to your computer and use it in GitHub Desktop.
React + D3

淺談 React + D3

這邊簡單說明 React 如何無痛與 D3 結合

思路

主要的作法有兩種

  • 由 React 控制 svg 的 DOM 結構
  • D3 控制 svg 的 DOM 結構,但需要實做更新邏輯

React 控制 <svg>

許多套件直接使用了這樣的做法,目的只有一個:維持 React 本身 propsstates 的特性,保有原本 React 的特性,但開發成本極高,以下簡單說明概念 。

class Chart extends Component {
  render() {  
		return (
			<svg>
			  <rect/>
			  <rect/>
			</svg>
		)
	}
}

在這個狀況下,我們無法在 Component 被 Mount 之後使用 D3 的 Data-driven 機制繪圖, 因為 React 的 DOM 結構邏輯位於 Virtual DOM 中。 此時,原先 D3 的 Data-driven 機制就必須重新實作,等於全部打掉重練。

render() {  
		return (
		<svg>
			{this.props.data.map( d=>(<rect/>) )}
		</svg>
)

如果想要重新撰寫一套以 React 的機制來實作 D3 的 JSX Component 的話,這的確是一個很棒的作法。 因為以 React 的精神,開發者不需要去管理複雜的 DOM 邏輯,一切 UI 的變化均由 states 決定,而這個 state 就可以對應到餵給圖表的資料。

每種圖表全部打掉重練
GitHub 上已經有許多這樣的專案,但是僅支援少數圖表種類

使用 React 但仍由 D3 控制 <svg>

回歸原先的 D3 並結合 React 是最有彈性的作法,因為只要了解正確觀念,網路上許多基於 D3 開發的套件仍然可以繼續使用, 不需要為了 React 全部打掉。雖然也有相關的 Tools 幫助開發者達到這樣的需求, 但其實原理並不難,你不一定要使用它們,以下簡單說明。

Lifecycle

首先,需要對於 React Component 的 生命週期 (Component Specs and Lifecycle) 有基本的認識, 在 React 的世界中,在 componentDidMount 被觸發後,才會產生真正的 DOM。也就是說, 雖然 React 要求使用者在其 Virtual DOM 的架構下思考,但不免仍會有必須操作到真正的 DOM 的情況。

import React, { Component } from 'react'
import { findDOMNode } from 'react-dom'

class Chart extends Component {

  componentDidMount(){
    var el = findDOMNode(this)   // el => <svg></svg>
  }
  render(){
    return <svg/>
  }
}

透過 ReactDOM 提供的 findDOMNode 方法, 可以取得最終被 render 出來的 DOM 元素。透過這個方式將 <svg> 取出交給原先的 D3 API 進行繪圖,就可以無痛結合。

  ...
  componentDidMount(){
     var el = findDOMNode(this)   // el => <svg></svg>
     draw(el)
  }
  render(){
     return <svg/>
  }
}

// d3 process as you know...
function draw(el){
  var svg = d3.select(el)
  svg.append('g')
  ...
}

完整的 Demo 可以參考這個 JSFiddle 上的範例 React D3 - Donut chart

Re-Render

但是,這樣的做法跳過了 Component 原先的 re-render 機制。 也就是說,將圖表資料綁定為 state 後,原先透過 setState 會驅動 render() 的流程,需要額外的控制。

class Chart extends React.Component{
  constructor(props) {
     super(props)
     this.state = {data:getData()}
  }
  
  componentDidMount(){
      var el = ReactDOM.findDOMNode(this)
      draw(el, this.state.data)
  }
  render(){ 	
      return <svg/>
  }
}

function getData(){
  return [
    Math.floor(Math.random() * 100),
    Math.floor(Math.random() * 100),
    Math.floor(Math.random() * 100)
  ]
}

圖表資料綁定 state 後,一旦 setState 被觸發後,就會重繪 Component。 依照這個思路,只需要將原先在 D3 上更新的動作交給 componentDidUpdate 觸發更新圖表即可。

對 React 來說 Component 只有一個 <svg> tag,即使改了 state 也根本不會有變化

class Chart extends React.Component{
  constructor(props) {
      super(props)
      this.state = {data:getData()}
  }
  
  componentDidMount(){
      var el = ReactDOM.findDOMNode(this)
      draw(el, this.state.data)
  }
  
  componentDidUpdate(){
      update(this.state.data)
  }
  
  render(){ 	
      return <svg/>
  }
}

透過以上這個概念,就可以無痛將原先撰寫的 D3 圖表整合到 React。 即使使用 Redux 架構也不用擔心,反而更輕鬆。 因為 Redux 幫助開發者將 States 自動綁定到 Props 上,並且偵測更新並重新 assign props, 因此只需要將範例中的 this.state.data 改為 this.props... 接收新資料並重繪即可。

完整的 Demo 可以參考這個 JSFiddle 上的範例 React D3 - Donut chart (advance)

      

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment