渝村 發表於 2022-10-14 18:39:00

React魔法堂:echarts-for-react源码略读

<h2 id="前言">前言</h2>
<p>在当前工业4.0和智能制造的产业升级浪潮当中,智慧大屏无疑是展示企业IT成果的最有效方式之一。然而其背后怎么能缺少ECharts的身影呢?对于React应用而言,直接使用ECharts并不是最高效且优雅的方式,而echarts-for-react则是针对React应用对ECharts进行轻量封装和增强的工具库。</p>
<p>echarts-for-react的源码非常精简,本文将针对主要逻辑分析介绍。</p>
<h2 id="从与原生初始化对比开始">从与原生初始化对比开始</h2>
<p>原生ECharts中我们会通过如下代码初始化图表实例</p>
<pre><code class="language-html">&lt;div id="container" style="width: 100px; height: 100px"&gt;&lt;/div&gt;
&lt;script&gt;
const chart = echarts.init(document.getElementById('container'))
&lt;/script&gt;
</code></pre>
<p>那么生成的HTML Element结构为</p>
<pre><code class="language-html">&lt;div id="container" style="width: 100px; height: 100px" _echarts_instance="....."&gt;
&lt;div style="width: 100px; height: 100px;position: relative;"&gt;
    &lt;canvas width="100" height="100"&gt;&lt;/canvas&gt;
&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>其中第二层的div和canvas的宽高默认为容器div#container的宽高,我们可以通过init入参指定两者宽度。</p>
<pre><code class="language-js">const chart = echarts.init(
document.getElementById('container'),
null,
{
    width: 300, // 可显式指定实例宽度,单位为像素。如果传入值为null/undefined/'auto',则表示自动取 dom(实例容器)的宽度
    height: 300 // 可显式指定实例高度,单位为像素。如果传入值为null/undefined/'auto',则表示自动取 dom(实例容器)的高度
}
)
</code></pre>
<p>注意:若此时容器div#container尺寸发生变化,第二层div和canvas尺寸并不会自适应,需要我们手工调用<code>chart.resize()</code>触发。</p>
<p>而通过echarts-for-react上述步骤将被简化为如下,并且生成相同的HTML Element结构:</p>
<pre><code class="language-jsx">import ReactECharts from 'echarts-for-react'

function Demo() {
return (
    &lt;ReactECharts
      style={{width: 100, height: 100}} // 设置容器的宽高
      autoResize={true} // 默认为true,自动监测容器尺寸的变化,并调用`chart.resize()`
    /&gt;
)
}
</code></pre>
<h3 id="陷阱-默认值height为300px">陷阱-默认值height为300px</h3>
<p>由于<code>ReactECharts</code>的<code>style</code>默认内置<code>height: 300</code>,源码如下:</p>
<pre><code class="language-tsx">// src/core.tsx

render(): JSX.Element {
const { style, className = '' } = this.props
const newStyle = { height: 300, ...style }

return (
    &lt;div
      ref={(e: HTMLElement) =&gt; {
      this.ele = e
      }}
      style={newStyle}
      className={`echarts-for-react ${className}`}
    /&gt;
)
}
</code></pre>
<p>因此通过className的方式设置容器高度时必须使用<code>!important</code></p>
<pre><code class="language-jsx">&lt;ReactECharts
className={styles.container}
/&gt;
</code></pre>
<pre><code class="language-css">// index.module.css
.container {
height: 500px !important;
}
</code></pre>
<h2 id="获取echarts实例">获取ECharts实例</h2>
<pre><code class="language-jsx">const ref = useRef()

useEffect(() =&gt; {
const instance = ref.current.getEchartsInstance()
}, [])

&lt;EchartsReact
ref={ref}
/&gt;
</code></pre>
<h2 id="主逻辑源码剖析">主逻辑源码剖析</h2>
<p>核心逻辑均在<code>EChartsReactCore</code>组件上(位于文件<code>src/core.tsx</code>),特点如下:</p>
<ol>
<li>采用PureComponent方式编写组件以便适配所有React版本;</li>
<li>仅对ECharts 命令式API进行声明式API的封装,并没有将每种EChart图表类型封装为组件;</li>
<li>添加特性,监测容器尺寸的变化,并自动调用ECharts实例的<code>resize</code>方法实现自适应。</li>
</ol>
<h3 id="挂载渲染过程">挂载渲染过程</h3>
<ol>
<li>在<code>componentDidMount</code>时调用<code>renderNewEcharts</code>方法执行ECharts组件的生成逻辑;</li>
<li><code>renderNewEcharts</code>方法内部逻辑
<ol>
<li>通过<code>echarts.getInstanceByDom(容器DOM元素)</code>或<code>echarts.init(容器DOM元素,主题,配置)</code>获取已有ECharts实例或生成新的ECharts实例;</li>
<li>通过ECharts实例的<code>setOption</code>方法设置或更新图表内容;</li>
<li>通过ECharts实例的<code>showLoading</code>或<code>hideLoading</code>控制图表渲染前是否显示加载进度条;</li>
<li>将通过props <code>onEvents</code>配置的ECharts支持的事件处理器绑定到ECharts实例上;</li>
<li>触发props <code>onChartsReady</code> 方法;</li>
<li>订阅通过<strong>size-sensor</strong>监测容器尺寸并自动调用ECharts实例的<code>resize</code>方法,实现图表尺寸的自适应。</li>
</ol>
</li>
</ol>
<h3 id="更新渲染过程">更新渲染过程</h3>
<p>由于<code>render</code>方法无论执行多少遍,实际上仅仅有可能影响容器本身而已,对ECharts实例并没有任何影响。因此实际影响ECharts实例的逻辑被放置到<code>componentDidUpdate</code>那里,这做法和react-amap中在<code>useEffect</code>中通过Marker等实例内置的<code>set</code>方法更新状态的原理是一致的。</p>
<ol>
<li>若更新的props包含<code>theme</code>, <code>opts</code>或<code>onEvents</code>则要销毁原来的ECharts实例,重新构建一个新的ECharts实例,并终止更新渲染过程;否则执行第2步。</li>
<li>若props中的<code>option</code>,<code>notMergela</code>,<code>lazyUpdate</code>,<code>showLoading</code>和<code>loadingOption</code>均没有变化,则不更新ECharts实例;<br>
注意:EChartsReactCore继承PureComponent,若上述props进行shallow equal比较为true时也不会更新ECharts实例;但这一步采用deep equal比较,来减少ECharts实例的更新。</li>
<li>若props中的<code>style</code>或<code>className</code>发生变化则会触发ECharts实例的<code>resize</code>方法。</li>
</ol>
<h3 id="卸载过程">卸载过程</h3>
<ol>
<li>取消通过<strong>size-sensor</strong>订阅的容器尺寸变化事件;</li>
<li>通过ECharts实例的<code>dispose</code>方法注销ECharts实例。</li>
</ol>
<h2 id="项目依赖">项目依赖</h2>
<ul>
<li><strong>fast-deep-equal</strong>: 遍历对象属性进行对比</li>
<li><strong>size-sensor</strong>: DOM元素尺寸监听器,当元素尺寸变化时会触发回调函数</li>
</ul>
<h2 id="后续">后续</h2>
<p>echarts-for-react利用<strong>size-sensor</strong>实现图表尺寸自适应容器尺寸,那么<strong>size-sensor</strong>是怎样做到这一点呢?敬请期待一下篇《React魔法堂:size-sensor源码略读》。</p>
<p>尊重原创,转载请注明来自:https://www.cnblogs.com/fsjohnhuang/p/16792575.html <sup>_</sup>肥仔John</p>


</div>
<div id="MySignature" role="contentinfo">
    <p>欢迎添加我的公众号一起深入探讨技术手艺人的那些事!<br/><img height="300" src="https://img2020.cnblogs.com/blog/347002/202012/347002-20201217175551329-1288702868.png"/></p>
<p>如果您觉得本文的内容有趣就扫一下吧!捐赠互勉!<br />&emsp;&emsp;<img src="http://images.cnitblog.com/blog/347002/201405/072251001672726.png" alt="" /></p><br><br>
来源:https://www.cnblogs.com/fsjohnhuang/p/16792575.html
頁: [1]
查看完整版本: React魔法堂:echarts-for-react源码略读