Vue组件使用D3.js访问DOM报Cannot read properties of null错误求助
报错原因
你触发报错的核心原因有两个:
- 你直接在模板的插值表达式
{{ chart() }}中调用绘图方法,此时组件DOM还未完成挂载,#sort对应的复选框元素还不存在,d3.select("#sort")会返回null,读取checked属性自然抛出类型错误 - 切换页面再返回时,旧组件的DOM已经被销毁,新组件挂载前就触发了
chart方法,同样拿不到对应的DOM元素
适配方案
refs完全适配你当前的场景,是Vue官方推荐的组件内访问DOM元素的方案,相比全局id选择器更安全,不会出现多组件同id冲突的问题,具体实现步骤如下:
第一步:修改模板,给目标DOM添加ref属性
给你需要用d3访问的所有DOM元素添加ref标识,替换原来的全局id选择逻辑:
<template> <div class="billabilityPlot" v-if="$store.state.monthlyBillability.items?.length > 0"> <!-- 删除此处插值内的chart调用,插值仅用来渲染数据,不要执行业务逻辑 --> </div> <div class="chart"> <h3>Billability plot</h3> <svg ref="chartSvg" width="800" height="400"></svg> Choose year: <select ref="yearSelect" id="year"></select> <input type="checkbox" ref="sortCheckbox" id="sort"> Toggle sort </div> </template>
第二步:调整chart方法的调用时机
把chart方法的调用放到mounted生命周期钩子中,此时组件DOM已经完全挂载,可以正常拿到ref对应的DOM元素;如果你的组件使用了keep-alive缓存,额外在activated钩子中调用即可适配路由切换场景:
export default { mounted() { // 保证store数据存在时再调用绘图 if (this.$store.state.monthlyBillability.items?.length > 0) { this.chart(this.$store.state.monthlyBillability.items) } }, // 用了keep-alive缓存组件时加这个钩子 activated() { if (this.$store.state.monthlyBillability.items?.length > 0) { this.chart(this.$store.state.monthlyBillability.items) } }, methods: { chart(billabilityData) { // 调用前先判断ref都存在,避免偶发时序问题 if (!this.$refs.chartSvg || !this.$refs.yearSelect || !this.$refs.sortCheckbox) return // 原有其他逻辑 const svg = d3.select(this.$refs.chartSvg) const yearSelect = d3.select(this.$refs.yearSelect) update(2021, 0) function update(year, speed) { // 原有其他逻辑 const isSortChecked = d3.select(this.$refs.sortCheckbox).property("checked") data.sort(isSortChecked ? (a, b) => b.billability - a.billability : (a, b) => months.indexOf(a.month) - months.indexOf(b.month) ) const bar = svg.selectAll(".bar") .data(data, d => d.month) bar.exit().remove(); } const checkbox = d3.select(this.$refs.sortCheckbox) .style("margin-left", "45%") .on("click", () => { // 修复原代码中未定义select变量的问题 update(yearSelect.property("value"), 750) }) } } }
额外注意点
- 不要在模板插值内执行绘图、计算等复杂逻辑,插值会在组件每次重渲染时重复执行,既浪费性能也容易触发时序问题
- 用ref访问DOM是组件内部隔离的,不会和页面其他组件的元素冲突,比全局id选择器稳定性更高
内容的提问来源于stack exchange,提问作者Babas
相关产品推荐
相关产品推荐

