在使用 v-for 指令处理列出数千个项目的组件时,我遇到了性能问题:更新某些项目会导致父组件的重新渲染。
我们可以举个例子:一个条形图,为客户光标周围的条形着色
Vue.component("BarChart", {
props: ["data", "width"],
data() {
return {
mousePositionX: null
};
},
template: `
<div class="bar-chart">
<div>Chart rendered: {{ new Date() | time }}</div>
<svg @mousemove="mousePositionX = $event.x" :style="{width: width}">
<bar
v-for="bar in bars"
:key="bar.id"
:x="bar.x"
:y="bar.y"
:height="bar.height"
:width="bar.width"
:show-time="bar.showTime"
:colored="bar.colored"
></bar>
</svg>
</div>
`,
computed: {
barWidth() {
return this.width / this.data.length;
},
bars() {
return this.data.map(d => {
const x = d.id * this.barWidth;
return {
id: d.id,
x: x,
y: 160 - d.value,
height: d.value,
width: this.barWidth,
showTime: this.barWidth >= 20,
colored: this.mousePositionX &&
x >= this.mousePositionX - this.barWidth * 3 &&
x < this.mousePositionX + this.barWidth * 2
}
});
}
}
});
Vue.component("Bar", {
props: ["x", "y", "width", "height", "showTime", "colored"],
data() {
return {
fontSize: 14
};
},
template: `
<g class="bar">
<rect
:x="x"
:y="y"
:width="width"
:height="height"
:fill="colored ? 'red' : 'gray'"
></rect>
<text v-if="showTime" :transform="'translate(' + (x + width/2 + fontSize/2) + ',160) rotate(-90)'" :font-size="fontSize" fill="white">
{{ new Date() | time }}
</text>
</g>
`
});
const barCount = 30; // to display the bars time, set barCount <= 30
new Vue({
el: "#app",
data() {
return {
data: Array.from({
length: barCount
}, (v, i) => ({
id: i,
value: randomInt(80, 160)
})),
width: 795
}
}
});
body {
margin: 0;
}
svg {
height: 160px;
background: lightgray;
}
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
Vue.config.devtools = true;
Vue.config.productionTip = false;
Vue.filter("time", function(date) {
return date.toISOString().split('T')[1].slice(0, -1)
});
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
</script>
<div id="app">
<bar-chart :data="data" :width="width" />
</div>
由于显示的时间值,我们可以看到组件重新渲染,这些时间值仅在相应组件被渲染时更新。
更新项目(条形图)颜色时,仅重新渲染更新的项目。
但是,这就是问题所在,即使没有更改任何项目,父级(条形图)也会在每次光标移动时重新渲染。
对于具有 30 个条形的条形图,它可能没问题。
但是如果显示大量条形图,重新渲染父组件所花费的时间太大,会导致严重的性能损失。
看看 1500 根柱线的相同示例:
Vue.component("BarChart", {
props: ["data", "width"],
data() {
return {
mousePositionX: null
};
},
template: `
<div class="bar-chart">
<div>Chart rendered: {{ new Date() | time }}</div>
<svg @mousemove="mousePositionX = $event.x" :style="{width: width}">
<bar
v-for="bar in bars"
:key="bar.id"
:x="bar.x"
:y="bar.y"
:height="bar.height"
:width="bar.width"
:show-time="bar.showTime"
:colored="bar.colored"
></bar>
</svg>
</div>
`,
computed: {
barWidth() {
return this.width / this.data.length;
},
bars() {
return this.data.map(d => {
const x = d.id * this.barWidth;
return {
id: d.id,
x: x,
y: 160 - d.value,
height: d.value,
width: this.barWidth,
showTime: this.barWidth >= 20,
colored: this.mousePositionX &&
x >= this.mousePositionX - this.barWidth * 3 &&
x < this.mousePositionX + this.barWidth * 2
}
});
}
}
});
Vue.component("Bar", {
props: ["x", "y", "width", "height", "showTime", "colored"],
data() {
return {
fontSize: 14
};
},
template: `
<g class="bar">
<rect
:x="x"
:y="y"
:width="width"
:height="height"
:fill="colored ? 'red' : 'gray'"
></rect>
<text v-if="showTime" :transform="'translate(' + (x + width/2 + fontSize/2) + ',160) rotate(-90)'" :font-size="fontSize" fill="white">
{{ new Date() | time }}
</text>
</g>
`
});
const barCount = 1500; // to display the bars time, set barCount <= 30
new Vue({
el: "#app",
data() {
return {
data: Array.from({
length: barCount
}, (v, i) => ({
id: i,
value: randomInt(80, 160)
})),
width: 795
}
}
});
body {
margin: 0;
}
svg {
height: 160px;
background: lightgray;
}
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
Vue.config.devtools = true;
Vue.config.productionTip = false;
Vue.filter("time", function(date) {
return date.toISOString().split('T')[1].slice(0, -1)
});
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
</script>
<div id="app">
<bar-chart :data="data" :width="width" />
</div>
对于 1500 条柱,Vue Devtools 清楚地表明重新渲染父组件所花费的时间太长(~278 毫秒)并导致性能问题。
那么,有没有办法更新子组件,这取决于父组件的数据(如光标位置),并避免父组件不必要的更新?
