Problem
问题始于react-apexcharts
对期权变化的react .以下是组件props 更新时运行的代码片段:
componentDidUpdate (prevProps) {
if (!this.chart) return null
const { options, series, height, width } = this.props
const prevOptions = JSON.stringify(prevProps.options)
const prevSeries = JSON.stringify(prevProps.series)
const currentOptions = JSON.stringify(options)
const currentSeries = JSON.stringify(series)
if (
prevOptions !== currentOptions ||
prevSeries !== currentSeries ||
height !== prevProps.height ||
width !== prevProps.width
) {
if (prevSeries === currentSeries) {
// series has not changed, but options or size have changed
this.chart.updateOptions(this.getConfig())
} else if (
prevOptions === currentOptions &&
height === prevProps.height &&
width === prevProps.width
) {
// options or size have not changed, just the series has changed
this.chart.updateSeries(series)
} else {
// both might be changed
this.chart.updateOptions(this.getConfig())
}
}
}
-reference-reference
从上面的代码片段可以看出,它将以前的和当前的选项转换为JSON字符串进行比较.当比例发生变化时,会引用一个新的格式化程序函数.然而,在JSON.stringify
中,函数被转换为空对象,因此结果JSON字符串在进行比较时是相同的.以下是一个演示,说明了这一点:
function formatter1() {}
function formatter2() {}
const prevOptions = {
chart: {
type: 'line',
},
series: [
{
name: 'serie1',
data: [1, 2, 3],
},
],
xaxis: {
categories: ['a', 'b', 'c'],
},
yaxis: {
labels: {
formatter: formatter1,
}
}
};
const currentOptions = {
chart: {
type: 'line',
},
series: [
{
name: 'serie1',
data: [1, 2, 3],
},
],
xaxis: {
categories: ['a', 'b', 'c'],
},
yaxis: {
labels: {
formatter: formatter2,
}
}
};
// Logs `true` even though the options have different formatters
console.log(JSON.stringify(prevOptions) === JSON.stringify(currentOptions));
Solution
An ok solution
通过了解react-apexcharts
个不同选项的不同之处,您可以向对象添加一个在比例更改时更改的关键点,以提示差异算法需要进行更新.在下面的演示中,scale
被添加到options
对象中(参见对setChartOptions
的调用):
const { createRoot } = ReactDOM;
const { Fragment, StrictMode, useEffect, useState } = React;
const Chart = ReactApexChart;
const App = () => {
const baseChartOptions = {
chart: {
type: 'line',
},
series: [
{
name: 'serie1',
data: [1, 2, 3],
},
],
xaxis: {
categories: ['a', 'b', 'c'],
},
};
const [chartOptions, setChartOptions] = useState(baseChartOptions);
const [scale, setScale] = useState('percentage');
useEffect(() => {
const getYAxisFormatter = (val) => {
return scale === 'percentage' ? `${val.toFixed(1)}%` : `$${val.toFixed(2)}`;
};
setChartOptions((prevOptions) => ({
...prevOptions,
yaxis: { labels: { formatter: getYAxisFormatter } },
scale,
}));
}, [scale]);
return (
<Fragment>
<h4>{scale}</h4>
<button onClick={() => setScale('dollars')}>Dollars</button>
<button onClick={() => setScale('percentage')}>Percentage</button>
<Chart options={chartOptions} series={chartOptions.series} type="bar" width="500" height="320" />
</Fragment>
);
};
const root = createRoot(document.getElementById("root"));
root.render(<StrictMode><App /></StrictMode>);
body {
font-family: sans-serif;
}
<div id="root"></div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/prop-types@15.8.1/prop-types.js"></script>
<script crossorigin src="https://unpkg.com/apexcharts@3.42.0/dist/apexcharts.js"></script>
<script crossorigin src="https://unpkg.com/react-apexcharts@1.4.1/dist/react-apexcharts.iife.min.js"></script>
A better solution
为什么chartOptions
需要存储在州中?您可能只需要将其作为一个普通的旧式JavaScript对象.这将通过消除设置和更新图表选项状态的代码来简化您的代码.从React's documentation人起:
避免冗余状态
如果您可以在呈现期间从组件的props 或其现有状态变量计算出一些信息,则可以将这些信息放入该组件的状态中.
如果你担心表现,那么getYAxisFormatter
附近的useCallback
和chartOptions
附近的useMemo
可能是有用的.但是,与所有性能优化一样,建议首先进行基准测试.
如上所述,仍然需要向差异算法提供组件需要重新呈现的提示.使用删除冗余状态的重构,这可以通过两种方式完成:
- 将
scale
作为键添加到chartOptions
对象(与ok解决方案类似).
- 从
scale
分到Chart
分的key
prop分.当REACT与DOM不同时,key
中的更改提示REACT组件需要重新呈现.这个选项可能比上面的选项更好,因为它是一个显式的API--如果react-apexcharts
更改了它的实现细节,前面的选项可能不起作用.以下是一个演示:
const { createRoot } = ReactDOM;
const { Fragment, StrictMode, useEffect, useState } = React;
const Chart = ReactApexChart;
const App = () => {
const [scale, setScale] = useState('percentage');
const getYAxisFormatter = (val) => {
return scale === 'percentage' ? `${val.toFixed(1)}%` : `$${val.toFixed(2)}`;
};
const chartOptions = {
chart: {
type: 'line',
},
series: [
{
name: 'serie1',
data: [1, 2, 3],
},
],
xaxis: {
categories: ['a', 'b', 'c'],
},
yaxis: {
labels: {
formatter: getYAxisFormatter
}
}
};
return (
<Fragment>
<h4>{scale}</h4>
<button onClick={() => setScale('dollars')}>Dollars</button>
<button onClick={() => setScale('percentage')}>Percentage</button>
<Chart key={scale} options={chartOptions} series={chartOptions.series} type="bar" width="500" height="320" />
</Fragment>
);
};
const root = createRoot(document.getElementById("root"));
root.render(<StrictMode><App /></StrictMode>);
body {
font-family: sans-serif;
}
<div id="root"></div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/prop-types@15.8.1/prop-types.js"></script>
<script crossorigin src="https://unpkg.com/apexcharts@3.42.0/dist/apexcharts.js"></script>
<script crossorigin src="https://unpkg.com/react-apexcharts@1.4.1/dist/react-apexcharts.iife.min.js"></script>