本文要在vue3+vite+JS项目中,使用Echarts封装一个饼图子组件,父组件中引入子组件并传入数据。传入的数据包括标题、高度、宽度、饼图数据,resize监听尺寸。子组件监听饼图数据的变化,来更新子组件的显示内容。
文章目录

一、实现思路
- 子组件封装:基于 ECharts 封装饼图组件,接收父组件传入的标题、宽高、饼图数据等 props;
- 响应式处理:监听饼图数据变化,自动更新图表;监听窗口 resize 事件,自适应调整图表尺寸;
- 父组件使用:导入子组件,传入自定义数据,实现灵活配置。
二、完整代码实现
1. 安装 ECharts 依赖
bash
npm install echarts --save
# 或 yarn add echarts
2. 饼图子组件(PieChart.vue)
vue
<template>
<!-- 图表容器,宽高由父组件传入 -->
<div
ref="chartRef"
class="pie-chart-container"
:style="{ width: width + 'px', height: height + 'px' }"
></div>
</template>
<script setup>
import { ref, watch, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'
// 定义接收的 props
const props = defineProps({
// 饼图标题
title: {
type: String,
default: '饼图示例'
},
// 图表宽度(px)
width: {
type: Number,
default: 400
},
// 图表高度(px)
height: {
type: Number,
default: 300
},
// 饼图数据(格式:[{ name: '分类1', value: 100 }, ...])
chartData: {
type: Array,
required: true,
validator: (val) => {
// 简单校验数据格式
return val.every(item => item.hasOwnProperty('name') && item.hasOwnProperty('value'))
}
}
})
// 图表实例引用
const chartRef = ref(null)
// ECharts 实例
let chartInstance = null
// 初始化图表
const initChart = () => {
// 确保容器存在
if (!chartRef.value) return
// 初始化 ECharts 实例
chartInstance = echarts.init(chartRef.value)
// 设置图表配置项
const option = {
title: {
text: props.title,
left: 'center' // 标题居中
},
tooltip: {
trigger: 'item', // 鼠标悬浮提示
formatter: '{a} <br/>{b}: {c} ({d}%)' // 提示格式:名称:数值(百分比)
},
legend: {
orient: 'vertical', // 图例垂直排列
left: 'left' // 图例居左
},
series: [
{
name: props.title,
type: 'pie',
radius: ['40%', '70%'], // 饼图内外半径
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 16,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: props.chartData // 饼图数据
}
]
}
// 渲染图表
chartInstance.setOption(option)
}
// 更新图表(数据变化时调用)
const updateChart = () => {
if (!chartInstance) return
// 仅更新数据相关配置,避免重复初始化
chartInstance.setOption({
title: { text: props.title },
series: [{ data: props.chartData }]
})
}
// 监听窗口 resize,自适应调整图表尺寸
const handleResize = () => {
if (chartInstance) {
chartInstance.resize()
}
}
// 生命周期:挂载时初始化图表 + 监听 resize
onMounted(() => {
initChart()
window.addEventListener('resize', handleResize)
})
// 生命周期:卸载时销毁实例 + 移除监听
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose() // 销毁 ECharts 实例,释放内存
chartInstance = null
}
window.removeEventListener('resize', handleResize)
})
// 监听 props 变化,更新图表
watch(
// 监听标题、宽高、数据的变化
[() => props.title, () => props.width, () => props.height, () => props.chartData],
() => {
// 宽高变化时先销毁再重新初始化,数据/标题变化时仅更新配置
if (props.width || props.height) {
chartInstance?.dispose()
initChart()
} else {
updateChart()
}
},
{
deep: true // 深度监听数组/对象变化
}
)
</script>
<style scoped>
.pie-chart-container {
/* 防止容器内边距影响尺寸 */
box-sizing: border-box;
/* 解决图表容器闪烁问题 */
transition: all 0.3s ease;
}
</style>
3. 父组件(ParentPage.vue)
vue
<template>
<div class="parent-page">
<h2>饼图组件使用示例</h2>
<!-- 操作区:修改数据/尺寸 -->
<div class="control-panel">
<button @click="changeChartData">切换饼图数据</button>
<button @click="changeChartSize">修改图表尺寸</button>
<button @click="changeChartTitle">修改图表标题</button>
</div>
<!-- 引入饼图子组件,传入配置 -->
<PieChart
:title="chartTitle"
:width="chartWidth"
:height="chartHeight"
:chart-data="chartData"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
// 导入饼图子组件
import PieChart from './PieChart.vue'
// 定义饼图配置数据(响应式)
const chartTitle = ref('各部门人数分布')
const chartWidth = ref(500)
const chartHeight = ref(400)
// 初始饼图数据
const chartData = ref([
{ name: '技术部', value: 30 },
{ name: '产品部', value: 15 },
{ name: '市场部', value: 20 },
{ name: '财务部', value: 8 },
{ name: '人事部', value: 7 }
])
// 模拟:切换饼图数据
const changeChartData = () => {
chartData.value = [
{ name: '技术部', value: 40 },
{ name: '产品部', value: 10 },
{ name: '市场部', value: 25 },
{ name: '财务部', value: 5 },
{ name: '人事部', value: 10 },
{ name: '运营部', value: 10 } // 新增分类
]
}
// 模拟:修改图表尺寸
const changeChartSize = () => {
chartWidth.value = 600
chartHeight.value = 450
}
// 模拟:修改图表标题
const changeChartTitle = () => {
chartTitle.value = '各部门人数分布(更新后)'
}
</script>
<style scoped>
.parent-page {
padding: 20px;
}
.control-panel {
margin-bottom: 20px;
}
.control-panel button {
margin-right: 10px;
padding: 8px 16px;
border: none;
border-radius: 4px;
background: #409eff;
color: #fff;
cursor: pointer;
}
.control-panel button:hover {
background: #66b1ff;
}
</style>
三、关键代码解释
-
子组件核心逻辑:
- props 定义 :明确接收标题、宽高、饼图数据,且对
chartData做格式校验,确保数据合法性; - 图表初始化 :
initChart方法创建 ECharts 实例并配置基础样式,updateChart方法仅更新数据/标题,避免重复初始化; - 响应式监听 :通过
watch深度监听 props 变化,宽高变化时重新初始化图表,数据/标题变化时仅更新配置; - 生命周期管理 :
onMounted时初始化图表并添加 resize 监听,onUnmounted时销毁 ECharts 实例、移除监听,防止内存泄漏; - resize 适配 :监听窗口尺寸变化,调用
chartInstance.resize()自动调整图表大小。
- props 定义 :明确接收标题、宽高、饼图数据,且对
-
父组件核心逻辑:
- 定义响应式数据(标题、宽高、饼图数据),传入子组件;
- 提供按钮模拟数据/尺寸/标题变化,验证子组件的响应式更新能力。
四、使用注意事项
- 数据格式 :父组件传入的
chartData必须是[{ name: 'xxx', value: xxx }, ...]格式,子组件已做基础校验; - 内存泄漏 :子组件在
onUnmounted中销毁 ECharts 实例、移除 resize 监听,避免页面卸载后残留事件; - 自适应优化 :如果需要监听父容器尺寸变化(而非窗口),可结合
element-resize-detector库实现; - 样式适配 :子组件容器的宽高使用
px单位,若需百分比,可修改 props 类型为 String(如width: '100%')。
五、封装总结
- 子组件封装了 ECharts 饼图的核心逻辑,通过 props 接收父组件配置,具备数据响应式更新、窗口 resize 自适应能力;
- 父组件可灵活传入自定义数据,通过修改响应式数据即可触发子组件更新,使用简单;
- 关键优化点:销毁 ECharts 实例、移除事件监听,避免内存泄漏;深度监听数据变化,保证图表实时更新。