From 468f7dbfbdcfaf758887990175e33c0cbc49311d Mon Sep 17 00:00:00 2001 From: Justineo Date: Wed, 17 Sep 2025 18:23:55 +0800 Subject: [PATCH] feat: add smart update --- README.md | 19 +++- README.zh-Hans.md | 17 +++- src/ECharts.ts | 115 ++++++++++++++++------ src/merge.ts | 243 ++++++++++++++++++++++++++++++++++++++++++++++ src/utils.ts | 4 + 5 files changed, 362 insertions(+), 36 deletions(-) create mode 100644 src/merge.ts diff --git a/README.md b/README.md index fb87c20..6e18b2b 100644 --- a/README.md +++ b/README.md @@ -151,13 +151,11 @@ See more examples [here](https://github.com/ecomfe/vue-echarts/tree/main/demo). - `option: object` - ECharts' universal interface. Modifying this prop will trigger ECharts' `setOption` method. Read more [here →](https://echarts.apache.org/en/option.html) - - > 💡 When `update-options` is not specified, `notMerge: false` will be specified by default when the `setOption` method is called if the `option` object is modified directly and the reference remains unchanged; otherwise, if a new reference is bound to `option`, `notMerge: true` will be specified. + ECharts' universal interface. Modifying this prop triggers Vue ECharts to compute an [update plan](#smart-updates) and call `setOption`. Read more [here →](https://echarts.apache.org/en/option.html) - `update-options: object` - Options for updating chart option. See `echartsInstance.setOption`'s `opts` parameter [here →](https://echarts.apache.org/en/api.html#echartsInstance.setOption) + Options for updating chart option. If supplied (or injected), Vue ECharts forwards it directly to `setOption`, skipping the [smart plan](#smart-updates). See `echartsInstance.setOption`'s `opts` parameter [here →](https://echarts.apache.org/en/api.html#echartsInstance.setOption) Injection key: `UPDATE_OPTIONS_KEY`. @@ -181,7 +179,18 @@ See more examples [here](https://github.com/ecomfe/vue-echarts/tree/main/demo). - `manual-update: boolean` (default: `false`) - For performance critical scenarios (having a large dataset) we'd better bypass Vue's reactivity system for `option` prop. By specifying `manual-update` prop with `true` and not providing `option` prop, the dataset won't be watched any more. After doing so, you need to retrieve the component instance with `ref` and manually call `setOption` method to update the chart. + For performance critical scenarios (having a large dataset) we'd better bypass Vue's reactivity system for `option` prop. By specifying `manual-update` prop with `true` and not providing `option` prop, the dataset won't be watched any more. After doing so, you need to retrieve the component instance with `ref` and manually call `setOption` method to update the chart (manual `setOption` calls are ignored when `manual-update` is `false`). + +#### Smart updates + +Vue ECharts analyses each change to `option` before invoking `setOption`: + +- Removed config objects (`legend`, `tooltip`, etc.) are replaced with `null` automatically so ECharts clears them. +- Removed arrays (`series`, `dataset`, …) are converted to empty arrays and marked via `replaceMerge` so old data disappears. +- If the diff looks risky (shrinking `options`/`media`, dropping scalar values), the component falls back to `notMerge: true` to rebuild the chart safely. +- Supplying `update-options` (or providing it via inject) bypasses the planner and uses your configuration as-is. + +Reactive updates use this logic by default. Manual `setOption` calls (only available when `manual-update` is `true`) behave like native ECharts (apart from slot patching) and honour only the per-call override you provide. ### Events diff --git a/README.zh-Hans.md b/README.zh-Hans.md index 1e5d1f9..f7ecb04 100644 --- a/README.zh-Hans.md +++ b/README.zh-Hans.md @@ -153,11 +153,9 @@ app.component('v-chart', VueECharts) ECharts 的万能接口。修改这个 prop 会触发 ECharts 实例的 `setOption` 方法。查看[详情 →](https://echarts.apache.org/zh/option.html) - > 💡 在没有指定 `update-options` 时,如果直接修改 `option` 对象而引用保持不变,`setOption` 方法调用时将默认指定 `notMerge: false`;否则,如果为 `option` 绑定一个新的引用,将指定 `notMerge: true`。 - - `update-options: object` - 图表更新的配置项。请参考 `echartsInstance.setOption` 的 `opts` 参数。[前往 →](https://echarts.apache.org/zh/api.html#echartsInstance.setOption) + 图表更新的配置项。一旦提供(或通过 inject 注入),Vue ECharts 会直接把它传给 `setOption`,并跳过[智能更新](#智能更新)。请参考 `echartsInstance.setOption` 的 `opts` 参数。[前往 →](https://echarts.apache.org/zh/api.html#echartsInstance.setOption) Inject 键名:`UPDATE_OPTIONS_KEY`。 @@ -181,7 +179,18 @@ app.component('v-chart', VueECharts) - `manual-update: boolean`(默认值`false`) - 在性能敏感(数据量很大)的场景下,我们最好对于 `option` prop 绕过 Vue 的响应式系统。当将 `manual-update` prop 指定为 `true` 且不传入 `option` prop 时,数据将不会被监听。然后,需要用 `ref` 获取组件实例以后手动调用 `setOption` 方法来更新图表。 + 在性能敏感(数据量很大)的场景下,我们最好对于 `option` prop 绕过 Vue 的响应式系统。当将 `manual-update` 指定为 `true` 且不传入 `option` prop 时,数据将不会被监听。此时需要用 `ref` 获取组件实例并手动调用 `setOption` 来更新图表(当 `manual-update` 为 `false` 时,手动调用 `setOption` 会被忽略)。 + +### 智能更新 + +Vue ECharts 在调用 `setOption` 之前会分析每次 `option` 变化: + +- 删除顶层对象(如 `legend`、`tooltip` 等)时,会自动写入 `null`,让 ECharts 清空旧配置。 +- 移除数组(如 `series`、`dataset` 等)时,会写入空数组并通过 `replaceMerge` 清除旧数据。 +- 如果检测到风险较高的变更(`options`/`media` 缩小、标量被删除等),会退回到 `notMerge: true` 以保证安全。 +- 如果提供了 `update-options`(或注入了默认值),会直接使用该配置并跳过智能计划。 + +默认情况下响应式更新都会使用这套逻辑。手动调用 `setOption`(仅当 `manual-update` 为 `true` 时可用)与原生 ECharts 一致,仅应用你在本次调用中传入的更新参数。 ### 事件 diff --git a/src/ECharts.ts b/src/ECharts.ts index 83ab7f9..c28881f 100644 --- a/src/ECharts.ts +++ b/src/ECharts.ts @@ -11,8 +11,10 @@ import { nextTick, watchEffect, toValue, + warn, } from "vue"; import { init as initChart } from "echarts/core"; +import type { EChartsOption } from "echarts"; import { usePublicAPI, @@ -25,6 +27,8 @@ import { import type { PublicMethods, SlotsTypes } from "./composables"; import { isOn, omitOn } from "./utils"; import { register, TAG_NAME } from "./wc"; +import { planUpdate } from "./merge"; +import type { Signature, UpdatePlan } from "./merge"; import type { PropType, InjectionKey } from "vue"; import type { @@ -71,24 +75,21 @@ export default defineComponent({ setup(props, { attrs, expose, slots }) { const root = shallowRef(); const chart = shallowRef(); - const manualOption = shallowRef