问题描述:
两个页面之间进行快速切换会报错元素找不到
问题原因:
该方法的调用是在nextTick中,也就是放在微任务队列中,当切换到该页面后主任务开始执行,(主任务执行完成后,更新dom元素,然后才是下一个tick),
然后快速切出该页面,组件销毁,此时dom元素处于尴尬位置,(可能是压根没有加载,也可能是加载后销毁)总之会元素消失,然而微任务队列还在等待执行,然后执行。此时代码中存在寻找dom元素的方法。dom已经销毁,这样就会引出错误。
1. 画布
<canvas :id="canvasId"></canvas>
2.画图方法
draw() {
// 因为该函数是放在微任务队列中,可能会存在元素销毁后该方法才执行,所以要判断一下元素是否存在,如果不存在则退出方法
const idEle = document.getElementById(this.canvasId)
if (!idEle && this.antData.length === 0) {
return
}
let height = this.antData.length >= 20 ? 390 : 270
this.antData.reverse()
this.chart = new F2.Chart({
id: this.canvasId,
width: this.screenWidth,
height: height,
pixelRatio: window.devicePixelRatio,
padding: [0, "auto", 20, "auto"],
})
let range = [0, 0.6]
if (this.judgeDataExitNegative(this.antData)) {
range = [0.1, 0.6]
}
this.chart.source(this.antData, {
label: {
type: "cat",
},
value: {
range: range,
},
})
this.chart.coord({
transposed: true,
})
this.chart.tooltip({
custom: true, // 自定义 tooltip 内容框
showTooltipMarker: true,
tooltipMarkerStyle: {
fill: "#F9EEDE",
fillOpacity: 0.99,
},
})
this.chart.legend(false)
// 添加文本标注
let offsetY2021 = 0
let offsetY2020 = 0
this.antData.forEach(item => {
if (item.name === moment().format("YYYY")) {
if (item.value < 0) {
this.chart.guide().html({
position: [item.label, item.value],
html: `<div style="line-height:14px;font-weight: bold;display:flex;font-size:12px;"><span style="display:inline-block;line-height:14px;color:${
item.disValue > 0 ? "#D04C63" : "#2e7065"
};padding:0 2px;font-weight: bold;">${item.disValue > 0 ? "+" : ""}${item.disValue ? item.disValue.toLocaleString() : "0"}</span><span style="color: #000000;"> ${
item.value ? item.value.toLocaleString() : "0"
}</span> </div>`,
offsetY: offsetY2021,
offsetX: 0,
alignX: "right",
alignY: "bottom",
})
} else {
this.chart.guide().html({
position: [item.label, item.value],
html: `<div style="font-weight: bold;display:flex;font-size:12px;align-items:center"><span style="color: #000000;"> ${
item.value ? item.value.toLocaleString() : "0"
}</span> <span style="display:inline-block;line-height:14px;color:${
item.disValue > 0 ? "#D04C63" : item.disValue === 0 ? "gray" : "#2e7065"
};padding:0 2px;margin-left: 4px;">${item.disValue > 0 ? "+" : ""}${item.disValue ? item.disValue.toLocaleString() : "0"}</span></div>`,
offsetY: 1,
offsetX: 2,
alignX: "left",
alignY: "bottom",
})
}
} else if (item.name === moment().subtract(1, "year").format("YYYY")) {
let alignX = "left"
if (item.value < 0) {
alignX = "right"
}
this.chart.guide().html({
position: [item.label, item.value],
html: `<span style="display:inline-block;font-weight: bold;font-size: 12px;text-align: center;color: #000000;height: 14px;line-height:14px">${
item.value ? item.value.toLocaleString() : "0"
}</span>`,
offsetY: offsetY2020,
offsetX: 2,
alignX: alignX,
alignY: "top",
})
}
})
this.chart.axis("label", {
line: { top: true },
// grid: {
// lineDash: true,
// stroke: "#e8e8e8",
// lineWidth: 1,
// fillOpacity: 0.6,
// },
})
this.chart.axis("value", {
line: { top: true },
})
this.chart.guide().line({
start: ["max", 0],
end: ["min", 0],
style: {
stroke: "gray",
lineWidth: 1,
lineCap: "round",
opacity: 0.3,
},
})
if (this.antData.length > 7) {
let { size, marginRatio } = this.getSizeAndRatio(this.antData.length)
// this.chart.interval().position("label*value").color("red")
this.chart
.interval()
.position("label*value")
.color("name", value => {
if (value === "2021") {
return "#89503c"
} else {
return "#CBAA7B"
}
})
.adjust({
type: "dodge",
marginRatio: marginRatio, // 设置分组间柱子的间距
})
.size(size)
// .style({
// radius: radius,
// })
} else {
this.chart
.interval()
.position("label*value")
.color("name", value => {
if (value === "2021") {
return "#9B5F4A"
} else {
return "#CBAA7B"
}
})
.adjust({
type: "dodge",
marginRatio: 0.02, // 设置分组间柱子的间距
})
}
this.chart.axis("label", {
label: {
fill: "#606266",
fontFamily: "PingFangSC-Medium, PingFang SC",
fontWeight: "500",
fontSize: "12",
},
})
this.chart.scale("label", { tickInterval: 0.11 })
this.chart.render()
this.chart.interaction("interval-select", {
startEvent: "tap", // 触发事件,默认为 tap 事件
selectAxis: true, // 是否高亮坐标轴文本
selectAxisStyle: {
fill: "#731E00",
fontWeight: "800",
},
unSelectStyle: { fillOpacity: 0.9 },
defaultSelected: {
...this.antData.reverse()[0],
},
cancelable: false,
onStart: e => {
this.$emit("interactionClick", e)
}, // 事件触发后的回调
mode: "range", // 选中策略,默认为 'shape', 即击中柱子才会触发交互
})
},
3. 方法的使用
init() {
this.clear()
this.$nextTick(() => {
this.draw()
})
}
4. 修改之前的代码片段
5. 修改后的代码片段
6. 问题解决
7. 感想:
在开发过程中使用一个官方方法时要知其所以然,多思考该方法可能存在的问题。就比如该处出现的问题,就应该综合vue的dom元素更新策略,watcher的执行时机,宏观任务和微观任务的区别等等......