ECharts 异步数据空状态完全指南
文档类型: 反模式警示
难度等级: ⭐⭐
源码版本: ECharts 5.x
本文行数: 约350行
📋 目录
🎯 问题描述
核心问题
当数据通过异步请求获取时,如果未处理加载状态,会导致:
- ❌ 图表区域空白
- ❌ 用户不知道是加载中还是无数据
- ❌ 体验极差
typescript
// ❌ 错误示范
async function loadChart() {
const chart = echarts.init(container);
// 异步获取数据
const data = await fetchData();
// 设置option
chart.setOption({
series: [{ data }]
});
// ⚠️ 在数据返回前,图表是空白的!
// 用户看到的是空白区域,不知道发生了什么
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❌ 错误示例
错误1: 无加载提示
typescript
// ❌ 用户看到空白,不知道要等多久
async function initChart() {
const chart = echarts.init(container);
const response = await fetch('/api/data'); // 可能需要3秒
const data = await response.json();
chart.setOption({
series: [{ data }]
});
// 这3秒内,用户看到的是空白!
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
错误2: 无错误处理
typescript
// ❌ 如果API失败,图表永远空白
async function initChart() {
const chart = echarts.init(container);
const data = await fetchData(); // 如果失败呢?
chart.setOption({
series: [{ data }]
});
// 没有try-catch,错误被吞掉
// 用户一直等待,不知道出错了
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
错误3: 无空数据处理
typescript
// ❌ 数据为空时也渲染图表
async function initChart() {
const chart = echarts.init(container);
const data = await fetchData();
chart.setOption({
series: [{ data }] // data可能是[]
});
// 如果data是空数组,图表显示空白
// 用户不知道是无数据还是bug
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
✅ 正确做法
方案1: 使用showLoading/hideLoading
typescript
async function initChart() {
const chart = echarts.init(container);
// ✅ 显示加载动画
chart.showLoading('default', {
text: '加载中...',
color: '#c23531',
textColor: '#000',
maskColor: 'rgba(255, 255, 255, 0.8)',
zlevel: 0
});
try {
const data = await fetchData();
chart.setOption({
series: [{ data }]
});
} catch (error) {
console.error('加载失败:', error);
// 显示错误提示
chart.setOption({
title: {
text: '加载失败,请刷新重试',
left: 'center',
top: 'middle'
}
});
} finally {
// ✅ 隐藏加载动画
chart.hideLoading();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
方案2: 空数据提示
typescript
async function initChart() {
const chart = echarts.init(container);
chart.showLoading();
try {
const data = await fetchData();
if (!data || data.length === 0) {
// ✅ 显示空数据提示
chart.setOption({
title: {
text: '暂无数据',
left: 'center',
top: 'middle',
textStyle: {
color: '#999',
fontSize: 16
}
},
graphic: {
elements: [{
type: 'text',
left: 'center',
top: '60%',
style: {
text: '当前时间段内没有数据',
fill: '#ccc',
fontSize: 12
}
}]
}
});
} else {
chart.setOption({
series: [{ data }]
});
}
} catch (error) {
showError(chart, error);
} finally {
chart.hideLoading();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
方案3: 骨架屏
typescript
function showSkeletonScreen() {
// 显示骨架屏而非loading动画
container.innerHTML = `
<div class="skeleton">
<div class="skeleton-title"></div>
<div class="skeleton-chart"></div>
<div class="skeleton-legend"></div>
</div>
`;
}
async function initChart() {
// 显示骨架屏
showSkeletonScreen();
try {
const data = await fetchData();
// 清空骨架屏
container.innerHTML = '';
const chart = echarts.init(container);
chart.setOption({
series: [{ data }]
});
} catch (error) {
showError(error);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
💻 实战案例
案例1: 完整的异步加载流程
typescript
import * as echarts from 'echarts';
class AsyncChartLoader {
private chart: echarts.ECharts | null = null;
private container: HTMLElement;
constructor(containerId: string) {
this.container = document.getElementById(containerId)!;
}
/**
* 加载图表数据
*/
async load(apiUrl: string) {
// 1. 初始化图表并显示loading
this.chart = echarts.init(this.container);
this.chart.showLoading('default', {
text: '数据加载中...',
color: '#5470C6',
textColor: '#333',
maskColor: 'rgba(255, 255, 255, 0.8)'
});
try {
// 2. 获取数据
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
// 3. 模拟延迟 (实际项目中删除)
await new Promise(resolve => setTimeout(resolve, 1000));
// 4. 检查数据
if (!data || data.length === 0) {
this.showEmptyState();
} else {
this.renderChart(data);
}
} catch (error) {
console.error('加载失败:', error);
this.showError(error as Error);
} finally {
// 5. 隐藏loading
this.chart?.hideLoading();
}
}
/**
* 渲染图表
*/
private renderChart(data: any[]) {
if (!this.chart) return;
this.chart.setOption({
title: {
text: '数据报表',
left: 'center'
},
tooltip: { trigger: 'axis' },
xAxis: { type: 'category' },
yAxis: { type: 'value' },
series: [{
type: 'line',
data: data,
smooth: true
}]
});
}
/**
* 显示空数据状态
*/
private showEmptyState() {
if (!this.chart) return;
this.chart.setOption({
title: {
text: '暂无数据',
left: 'center',
top: 'middle',
textStyle: {
color: '#999',
fontSize: 18
}
},
graphic: {
elements: [
{
type: 'image',
left: 'center',
top: '35%',
style: {
image: '/empty-state.svg',
width: 100,
height: 100
}
},
{
type: 'text',
left: 'center',
top: '60%',
style: {
text: '当前筛选条件下没有数据',
fill: '#999',
fontSize: 14
}
}
]
}
});
}
/**
* 显示错误状态
*/
private showError(error: Error) {
if (!this.chart) return;
this.chart.setOption({
title: {
text: '加载失败',
subtext: error.message,
left: 'center',
top: 'middle',
textStyle: {
color: '#EE6666',
fontSize: 18
},
subtextStyle: {
color: '#999',
fontSize: 12
}
},
graphic: {
elements: [{
type: 'text',
left: 'center',
top: '65%',
style: {
text: '请点击重试或联系管理员',
fill: '#999',
fontSize: 12
}
}]
}
});
}
dispose() {
if (this.chart) {
this.chart.dispose();
this.chart = null;
}
}
}
// 使用
const loader = new AsyncChartLoader('chart-container');
loader.load('/api/sales-data');1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
🎯 最佳实践总结
✅ DO - 推荐做法
始终显示加载状态
typescriptchart.showLoading(); await fetchData(); chart.hideLoading();1
2
3处理空数据情况
typescriptif (data.length === 0) { showEmptyState(); }1
2
3提供错误提示
typescriptcatch (error) { showError(error); }1
2
3
❌ DON'T - 避免做法
- 不要让用户看到空白typescript
// ❌ 不好 const data = await fetchData(); chart.setOption({ series: [{ data }] }); // ✅ 好 chart.showLoading(); const data = await fetchData(); chart.hideLoading();1
2
3
4
5
6
7
8
🔗 相关资源
下一篇: 地图未注册白屏
