【React】戲說組件式百度圖表的由來及簡單邏輯

舒適提示:若是您對EchartsReact足夠了解,而且想直接了當的看組件式百度圖表react-component-echarts的解決方案,那麼爲了避免浪費您寶貴的時間,建議您跳過前面的部分,直接從解決方案部分開始看起。javascript

前言

在衆多數據可視化產品中,Echarts可謂中流砥柱,其功能的強大、靈活在圖表界的水平以頂尖來形容,我的感受一點也不過度;我做爲一名前端開發人員,也常常用Echarts來解決公司或我的項目中的圖表需求,甚是駕輕就熟;不得不說,在我從事研發工做多年以來,還能保持着一頭烏黑的短髮,Echarts有一部分的功勞,我很是感謝她,幫我解決了很多煩惱。html

後來,我遇到了React,她聲明式組件化一次學習,隨處編寫的三大特性一下將我拉向了一個全新的世界,恍惚間我隱約明白,原來世界還能這樣運行;我不斷的瞭解着她,天天與她促膝長談、通力合做,完成了一個又一個項目;是時候了,該向她介紹個人另外一位老朋友與她認識了;沒錯,就是Echarts,我以爲你們會很愉快的接納對方;但,事與願違。前端

我彷佛聞到了一絲火藥味;是了,她們都是強者,性格各異,我做爲中間人,要磨合她們。java

Echarts

Echarts的工做方式十分簡單,你只須要告訴她要什麼圖表、展現什麼數據就能夠了,毫秒之間,本來乾枯的數據就會變成繪聲繪色的可視化圖表,讓人有一種成就感滿滿的錯覺,就像下面這樣:react

const dom = document.getElementById('container')
 const myChart = echarts.init(dom)
 const option = {
            xAxis: {
                type: 'category',
                data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
            },
            yAxis: {
                type: 'value'
            },
            series: [{
                data: [820, 932, 901, 934, 1290, 1330, 1320],
                type: 'line'
            }]
        }
myChart.setOption(option, true)
複製代碼

若是將這段代碼放在一個完整的HTML中,打開瀏覽器,你會看到一幅美麗的圖表,跟下面這張圖片同樣,並且瀏覽器中仍是帶動畫的: git

Basic Line Chart
單看一堆數字,很難看清楚他在表達什麼,若是將他轉換成圖表,那麼結果就不言而喻了,這就是類 Echarts庫的魅力所在。

React

在全新的世界中,我着重說一點:組件化,這是我喜歡React的一大緣由;何爲組件化?我這裏作個不太恰當的比喻:零件;一個機械化的手錶,裏面由若干個精密的齒輪、發條完美的結合在一塊兒,各自分工,最終都有一個共同的目標:表達時間;咱們寫組件,就是在作零件,能夠本身作,也能夠拿別人作好的;再經過本身的加工打磨,組合在一塊兒,就完成了一個目標,最終構成一個複雜的界面;這就是我所理解的,使用React最基本、最主要的工做模式。github

一個簡單的React組件就像這樣:npm

// 顯示當前時間
class DateNow extends Component {
    constructor(props) {
        super(props)
        this.state = {
            now: Date.now()
        }
    }
    componentDidMount() {
        setInterval(() => this.setState({ now: Date.now() }), 1000)
    }
    render() {
        const { name } = this.props
        const { now } = this.state
        return (
            <div className="date-now"> <p>當前時間:{Date(now)}</p> <p>你好:{name}</p> </div>
        )
    }
}
// 當前時間:Sun Apr 07 2019 16:30:10 GMT+0800 (中國標準時間)
// 你好:安妮
<DateNow name="安妮" />
複製代碼

這樣一個零件就製做好了,哪裏使用安哪裏。一個設計巧妙的組件,總應該儘量的發揮它本身的做用,這纔是寫一個組件的意義,不過現實是,當你前期竭盡全力的編寫好了不少個組件,考慮了足夠多的應用場景,準備在未來一展拳腳,向老闆展現本身真正的實力的時候,回過頭來才發現,項目...亡了,這是一個足夠悲傷又透露着好笑的故事,然而這也是現實中絕大多數項目的一種狀況,因此說咱們在從頭開始作一個項目的時候,真的要好好考慮它的實際狀況,一上來就從開山掘石的起點去蓋樓是極不明智的,它或許沒有那麼多的時間來等你作準備工做,其實拿來主義的背後透露着三個字:更可靠;公司的項目可以準時交付、業務線能穩定增加,是須要更成熟的輪子來做爲鋪墊的,即使你的能力再強、邏輯再縝密、造的輪子零BUG,那也是須要時間的,而每每不少項目來不及等就要黃了,更況且本身趕時間造的輪子漏洞百出;總之,個人見解就是:項目與學習要分清,項目就是要更靠譜,學習才能去造輪子,說難聽點,項目不是你練手的地方。redux

撤遠了,這不是我們如今討論的主題,談到組件就由感而發,還請閱者批正;不過話說回來,若是在工做之餘,可以造一些基礎輪子仍是頗有必要的,這種輪子不屬於任何一類項目,只是很明確的解決一種需求或完成一個基本功能,這是一勞永逸的事情。數組

再回到正題,我們接下來的主題是:介紹EchartsReact成爲好朋友,讓她們更默契的爲你們工做。

強行組合

爲何說要讓她們更默契呢,這裏不得不將她們拉出來讓你們看看,爲何我這麼說:

class Exmaple extends Component{
    componentDidMount(){
        const myChart = echarts.init(this.chartDom)
        const option = {
                    xAxis: {
                        type: 'category',
                        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
                    },
                    yAxis: {
                        type: 'value'
                    },
                    series: [{
                        data: [820, 932, 901, 934, 1290, 1330, 1320],
                        type: 'line'
                    }]
                }
        myChart.setOption(option, true)
    }
    render(){
        return (
            <div ref={chartDom=>this.chartDom=chartDom}/> ) } } 複製代碼

這樣的代碼是能運行,可是EchartReact基本上是本身幹本身的,徹底沒有要相互靠攏的意思,這還怎麼更好的玩耍呢?並且大段的option也很難維護,在實際項目中,若干個屬性值仍是動態數據,好比這裏的series[].data,萬一圖表複雜,動態數據更多,若是頁面上還有不少其它元素,那最後你就會發現本身寫了一坨代碼,事後本身都不想再看,更別說讓別人維護了;即使把圖表相關的代碼拿出來另起文件,同樣是各玩各的,極不和諧。

有道是:代碼千萬行,註釋第一行;編碼不規範,同事兩行淚,何解呢?

遇到問題老是要解決的,即使測試的小姐姐們以容嬤嬤的步伐緩緩走來、面帶蒙娜麗莎般的微笑告訴我:上線吧。但那一坨魔同樣存在的代碼老是時不時的在我內心冒出來嬉笑本身一番,真真的飯也不香、夜也不眠吶;想辦法解決它吧,誰叫咱是有追求的人呢?

剎那間,妖風四起,天空中滾滾烏雲壓將下來,我一陣翻雲倒海,與那一坨魔同樣的代碼斗的你死我活,就在我快要支撐不住的時候,突然感受右肩一沉,嚇的我一個機靈,而後一聲低沉的聲音入耳:發什麼楞呢?嗚呼~,原來是本身走火入魔了,不過我已經想到了解決方案。

解決方案

直觀來說,Echarts爲配置式開發,主要維護一個option就能夠了;React爲組件式開發,對外可接收props參數,內部經過state控制邏輯,再加上jsx來編寫頁面結構,簡直完美!那麼,何不以組件式的形式來完成Echarts的開發?就像這樣:

配置式與組件式對比

單從行數上來看,已經少了不少,可維護性也更高,將一個龐大的配置對象拆分紅一個個屬性,開發人員只須要關心可變化的那一部分便可,或經過state,或經過redux等來存儲數據,邏輯一會兒清晰了不少,那種熟悉又親切的感受找回來了!

想法是好的,如何實現呢?

將配置與jsx仔細一對比會發現,其實它們的結構是類似的,能夠說是一至的,Recharts能夠做爲option對象自己,那麼它的子節點XAxisYAxisSeries就對應着option對象的三個屬性,而這三個屬性的值在jsx上又以props的形式來體現,一一對應起來了,簡單來講,代碼是這樣子的:

import React, { PureComponent } from 'react'
export default class Recharts extends PureComponent {
    constructor() {
        super()
        this.option = {}
    }
    componentDidMount(){
        this.chart = echarts.init(this.dom, ...)
        this.chart.setOption(this.option)
    }
    render(){
        const {children} = this.props
        return (
            <div ref={dom=>this.dom=dom}>{children}</div>
        )
    }
}
複製代碼

這裏主要作了兩件事,一是保存了配置option對象,一是建立並初始化了圖表,根節點的任務算是完成了大半了,可是這個時候option是個空對象,裏面什麼也沒有,圖表天然也出不來,它的數據主要來自子節點,那麼子節點的任務就很明瞭了,將接收到的props原封不動向上傳遞,通通砸向父節點就完了。

問題來了,如何向上傳遞?React的特性是自上而下,父節點想給子節點傳遞數據很簡單,經過props傳就能夠了,可是子節點向父節點傳呢?只能經過函數來傳遞了,我也不得不這麼作:

// 根節點
export default class Recharts extends PureComponent {
    ...
    // 接收子節點配置
    // @name 子節點名稱
    // @option 子節點配置
    handleReceiveChildOption = (name, option) => {
        // 進一步處理
        ...
    }
    render(){
        return (
            <div ref={dom => (this.dom = dom)}> {React.Children.map(this.props.children, children => { if (isValidElement(children)) { return React.cloneElement(children, { triggerPushOption: this.handleReceiveChildOption }) } return children })} </div>
        )
    }
}

// 子節點
export default class BaseComponent extends PureComponent {
    componentDidMount(){
        const { triggerPushOption, children, ...props } = this.props
        if(triggerPushOption) {
            triggerPushOption(this.name, props)
        }
    }
    render(){
        return this.props.children
    }
}
複製代碼

這個時候,子節點的任務基本就完成了,它只須要簡單的向上傳遞就能夠了,無需再作其它處理,其實這裏子節點還須要考慮一種狀況就是子節點也有子節點:<Toolbox><Feature/></Toolbox>,要處理這種狀況很簡單,像父節點那樣,給children傳遞一個函數,在函數裏接收數據,合併後繼續向上傳遞就能夠了,這裏再也不給出代碼。

是否是就大功告成了?是的,主要邏輯就是這麼簡單,Echarts自己就很強大,我作的只不過是按需將jsx生成option並適時建立圖表就能夠了;固然,實際代碼要複雜一些,由於我還考慮了子節點是數組的時候如何處理、容器改變大小時如何resize圖表、如何合併option以達到減小setOption操做的目的等等,還有很重要的一點就是,option屬性雖多,但作爲子節點它們的行爲都是一致的,我只須要將屬性羅列下來,經過數組來擴展BaseComponent就能夠了,細節邏輯也比較簡單,就不在這裏細說了,感興趣的朋友能夠看源碼

輔助工具

在我喪心病狂的擼完了代碼,心喜若狂的準備大幹一番的時候才發現,原來我又給本身製造了一個煩惱,這話怎麼說?我相信絕大多數同窗在寫Echarts圖表時,必定是從官方示例中找一個跟本身需求相似的圖表,將其option複製粘貼到本身項目中,再按需改改,一個圖表的功能就完成了,快速直接,簡直一個字:美!

可是換成jsx的寫法時..., WTF!我須要將一個個的屬性從新寫成jsx,而後再一個個的把屬性值寫成props,當我把一個比較複雜的圖表所有改爲組件式後,個人狀態是:我是誰?我在哪?我在作什麼?若是就此結束,那麼react-component-echarts也就沒什麼做用了,緣由三個字:太難用。

我沒有放棄!

伴隨着憤怒的鍵盤敲擊與MacBook風扇嗡嗡做響的哀嚎聲中,爲react-component-echarts專門服務的輔助工具應運而生了,它長這個醜樣:

輔助工具

輔助工具主要由三部分組成:

  • 左側:可編輯,此處粘貼本身的option
  • 右上:不可編輯,根據左側option生成所依賴的組件
  • 右下:不可編輯,根據左側option生成組件式圖表代碼

還有隱藏的第四部分與第五部分,一個是提示替換變量,一個是option代碼有錯誤,生成代碼失敗時會出現,分別是:

提示替換變量

代碼有錯誤

代碼有錯誤這個就不用多解釋了,檢查本身的option有什麼問題改過來就行了;提示替換變量須要說明一下,比如你從官網示例中複製了一段配置,配置裏面某一項的值是取的外部變量,那麼這個時候在輔助工具的運行環境是沒有這個變量的,生成時就會出錯,我在這裏作了一個容錯處理,發現有變量未定義這種錯誤時,會自動建立一個同名變量,而且在生成圖表代碼時,變量以$varName$的形式存在,當你發現有這個提示時,只須要把以$開頭結尾的字符串替換爲真實變量就能夠了。

今後,EchartsReact終於愉快的生活,哦不,愉快的工做在一塊兒了。

後記

本人水平有限,文筆有限,以上文字還請海涵;另外,若是發現react-component-echarts邏輯有什麼問題或者有什麼更好的建議,還請明示,很是感謝!

React Component Echarts 相關連接 (給個 Star 唄):

相關文章
相關標籤/搜索