react封装函数_如何在react中封装echarts

echarts一款非常不错的可视化图表库,本文以echarts3.x版本为例,通过分析echarts-for-react源码介绍如何在react中封装使用echarts

目录

为什么要封装

旧的思维

习惯了在jquery开发模式下导入echarts、swiper、d3等需要操作到真实dom的第三方库,突然切换到React,会按照旧的思维模式使用,导致一些问题产生。先看看不封装的情况下在React中使用echarts:

import React, { Component } from 'react'

import echarts from 'echarts'

export default class OldChart extends Component {

componentDidMount () {

// 基于准备好的dom,初始化echarts实例

var myChart = echarts.init(document.getElementById('old-chart'))

// 绘制图表

myChart.setOption({

title: { text: 'ECharts 入门示例' },

tooltip: {},

xAxis: {

data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']

},

yAxis: {},

series: [{

name: '销量',

type: 'bar',

data: [5, 20, 36, 10, 10, 20]

}]

})

}

render () {

return (

)

}

}

结果截图如下:

这种方式先通过document.getElementById()的形式先获取到真实的dom,然后通过echarts.init()方法实例化。

例子中给出的是静态数据,如果数据是动态获取的,那就要把渲染echarts的数据放入state中,然后render()中通过echarts.setOption(this.state.option)方法来更新图表

存在的问题

echarts初始化需要访问DOM,为了整合到React中,我们需要在componentDidMount获取准备好的DOM,然后传递给echarts,再通过echarts的api来生成图,可以看出,实例化的方式有些繁琐

代码完全不能复用,每实例化一个图表,我们都需要做一些重复的事情:在componentDidMount上获取准备好的dom,传递给echarts

拓展性差,如果要监听windows.resize事件或者主题变更,我们可能需要一个个类操作过去进行变更

需要达到的目的

简单好用,调用起来越简单越好

复用性强,拥有基本的能力:比如监听resize

如何实现

第一步、最简单的功能

/* test.js */

import React, {Component} from 'react'

import PropTypes from 'prop-types'

import echarts from 'echarts'

export default class ReactEcharts extends Component {

constructor (props) {

super(props)

this.echartsInstance = echarts // echarts object

this.echartsElement = null // echarts dom

}

componentDidMount () {

// 获取dom容器上的实例,如果没有,则实例化

const echartObj = this.echartsInstance.getInstanceByDom(this.echartsElement) || this.echartsInstance.init(this.echartsElement)

// echarts的万能接口,设置图表实例的配置项以及数据,所有参数和数据的修改都通过setOption完成

echartObj.setOption(this.props.option)

}

render() {

const style = this.props.style || {

height: '300px'

}

// for render

return (

ref={(e) => { this.echartsElement = e }}

style={style}

className={this.props.className}

/>

)

}

}

ReactEcharts.propTypes = {

option: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types

style: PropTypes.object, // eslint-disable-line react/forbid-prop-types

className: PropTypes.string

}

ReactEcharts.defaultProps = {

style: {height: '300px'},

className: ''

}

这里我们做了这几件事:

1. 创建一个名为ReactEcharts的react组件,包含两个属性echartsInstance、echartsElement

> echartsInstance属性建立与echarts的连接,能通过该属性调用echarts的api

> echartsElement获取图表所在的dom,我们给一个初始值null

2. 在render()中,通过ref设置一个回调函数,来获取dom元素,并将值赋予echartsElement属性

> 挂到react组件上的ref表示对组件实例的引用,而挂载到dom元素上时表示具体的dom元素节点。

3. 在componentDidMount()中实例化echarts,判断当前dom元素是否已经有echarts实例,如果有则用已有的,没有则init,然后调用echarts的setOption()来渲染图,参数我们通过this.props.option获取

ok, 搞定,理论上我们只需要调用这个组件时传递一个option就可以在页面上看到效果了,我们试一下:

import ReactEcharts from './test'

const option = {

title: { text: 'ECharts 入门示例' },

tooltip: {},

xAxis: {

data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']

},

yAxis: {},

series: [{

name: '销量',

type: 'bar',

data: [5, 20, 36, 10, 10, 20]

}]

}

export default class testChart2 extends Component {

render () {

return (

option={option}

style={{ width: 400, height: 400 }} />

)

}

}

运行起来没有问题,在这里我们只要修改option的配置,就可以轻松更新图表了,option是动态的话也是没有问题的,只要你能将它的更新传递到ReactEcharts中即可

第二步、为组件增加一些常用的功能

接下来,我们加快进度,一次性为ReactEcharts增加一系列功能:

1. theme主题定制

2. 完善echarts.setOption()方法能力,增加notMerge, lazyUpdate参数,官方文档链接

> notMerge可选,是否不跟之前设置的option进行合并,默认为false,即合并。

lazyUpdate可选,在设置完option后是否不立即更新图表,默认为false,即立即更新。

3. 组件unmount时销毁相关echarts实例

4. 实现加载动画效果,当数据量大或者异步的时候,显示Loading

其中,这四点实现起来比较简单,参照echarts文档,在我们的componentDidMount()中作一些处理即可,这里就不详细讲了

我们直接上代码,大家看看就明白了:

export default class ReactEcharts extends Component {

constructor (props) {

super(props)

this.echartsInstance = echarts // echarts object

this.echartsElement = null // echarts dom

}

// first add

componentDidMount () {

const echartObj = this.renderEchartDom()

}

// update

componentDidUpdate () {

this.renderEchartDom()

}

// 3. 组件unmount时销毁相关echarts实例

componentWillUnmount () {

if (this.echartsElement) {

this.echartsInstance.dispose(this.echartsElement)

}

}

// 1. theme主题定制

getEchartsInstance = () => this.echartsInstance.getInstanceByDom(this.echartsElement) ||

this.echartsInstance.init(this.echartsElement, this.props.theme);

// render the dom

renderEchartDom = () => {

// init the echart object

const echartObj = this.getEchartsInstance()

// 2. 完善echarts.setOption()方法能力,增加`notMerge`, `lazyUpdate`参数

echartObj.setOption(this.props.option, this.props.notMerge || false, this.props.lazyUpdate || false)

// 4. 实现加载动画效果,当数据量大或者异步的时候,显示Loading

if (this.props.showLoading) echartObj.showLoading(this.props.loadingOption || null)

else echartObj.hideLoading()

return echartObj

}

render () {

// 和上面一样,这里就不重复了...

}

}

ReactEcharts.propTypes = {

option: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types

echarts: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types

notMerge: PropTypes.bool,

lazyUpdate: PropTypes.bool,

style: PropTypes.object, // eslint-disable-line react/forbid-prop-types

className: PropTypes.string,

theme: PropTypes.string,

showLoading: PropTypes.bool,

loadingOption: PropTypes.object, // eslint-disable-line react/forbid-prop-types

}

ReactEcharts.defaultProps = {

echarts: {},

notMerge: false,

lazyUpdate: false,

style: {height: '300px'},

className: '',

theme: null,

showLoading: false,

loadingOption: null,

}

第三步、为组件封装事件

到目前为止,我们已经封装了一个挺好用的“静态”图表了,这里的静态指的是图表完全不能交互,dom尺寸变动图表也不会更新,显然这不太友好。

所以我们需要实现以下两个功能

1. echarts的事件处理

2. 监听窗口变动事件,使得dom尺寸发生变化时,图表能够更新

直观的,我们只要对外暴露一个echarts实例的接口,让外部自己通过该接口去绑定事件就可以。

但是这样对于外部调用者来说就比较繁琐,从使用者的角度,希望能够只要传入一事件对象,就能帮忙监听所有事件

为了达到这个目的,我们可以这样做:

第一个功能、echarts的事件处理

先定义一个事件对象,数据结构如下:

{

'eventName': callback, // callback是回调函数

// 例如:

click: function () {...},

legendselectchanged: function () {...},

...

}

其中属性值对应echarts支持的事件名echarts event api

假设ReactEcharts组件接受一个props.onEvents的事件对象,我们为ReactEcharts组件增加一个方法bindEvent()

```javascript

componentDidMount () {

const echartObj = this.renderEchartDom()

const onEvents = this.props.onEvents || {}

this.bindEvents(echartObj, onEvents)

}

bindEvents = (instance, events) => {

const _loopEvent = (eventName) => {

if (typeof eventName === ‘string’ && typeof events[eventName] === ‘function’) {

instance.off(eventName)

instance.on(eventName, (param) => {

// 触发回调

eventseventName

})

}

}

for (const eventName in events) {

if (Object.prototype.hasOwnProperty.call(events, eventName)) {

_loopEvent(eventName)

}

}

}

```

代码理解起来不难,遍历onEvents对象,并通过echarts.on()接口将eventName和回调函数callback绑定,除了保留echarts自身事件的事件参数param,我们还加上一个instance参数,提供更强的能力

接下来看一下外部如何调用:

export default class YourComponentName extends Component {

componentDidMount () { /** ... **/ }

getOption () { /** ... **/ }

onChartClick (param, echart) {

console.log('onChartClick', param, echart)

}

onChartLegendSelectChanged (param, echart) {

console.log('onLegendSelect', param, echart)

}

render () {

const onEvents = {

click: this.onChartClick,

legendselectchanged: this.onChartLegendSelectChanged

}

return (

option={this.getOption()}

style={{height: '800px', width: '100%'}}

onEvents={onEvents}

className='react-echarts'

/>

)

}

}

这样我们就完成了事件处理

第二个功能、resize事件监听

接下来再处理一下resize,当外部容器尺寸变化或者用户窗口变化的时候图表能够自动更新,如何监听dom尺寸的变化,我们在本文就不详细讲解了,这里我们直接调用一个npm包element-resize-event来帮助我们监听dom尺寸变化

引入这个包,将ReactEcharts组件的componentDidMount()修改如下:

import elementResizeEvent from 'element-resize-event'

// ...

componentDidMount () {

const echartObj = this.renderEchartDom()

const onEvents = this.props.onEvents || {}

this.bindEvents(echartObj, onEvents)

// on resize

elementResizeEvent(this.echartsElement, () => {

echartObj.resize()

})

}

componentWillUnmount () {

if (this.echartsElement) {

// if elementResizeEvent.unbind exist, just do it.

if (typeof elementResizeEvent.unbind === 'function') {

elementResizeEvent.unbind(this.echartsElement)

}

this.echartsInstance.dispose(this.echartsElement)

}

}

// ...

同时别忘了在unmount时解绑事件

好了,到目前为止,我们完成了大部分工作,我们的ReactEcharts组件能够应付大部分的应用场景,但是还有一些细节没有处理(诸如chartReady,onResizeCallback)

> chartReady图表实例化之前触发

onResizeCallback窗口变动后触发,可用于图表内部坐标点的重新计算

本文就不详细讲了,最新代码可以到这里查看

总结

当使用React遇到需要使用第三方库的时候,可以根据第三方库的api以及从使用者的角度来进行一些简单的封装,使得这些第三方库结合React使用起来更加方便

封装的时候需要预见一些可能需要实现的功能,即时现在不用,将来也需要扩展

你可能感兴趣的:(react封装函数)