You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue组件中$refs引用失效问题咨询(非单文件组件场景)

问题:Vue组件mounted钩子中访问$refs.dial_canvas返回undefined?

我写了一段简化的Vue代码,想用自定义组件渲染一个表盘,但是运行后控制台显示this.$refs.dial_canvas是undefined,连$el也变成了undefined。我已经注意到不在created里访问refs,而是用了mounted,但还是不行。另外我不想用.vue文件和服务端编译的方式,只想用静态文件+最简Web服务器,组件要能复用展示不同值。

我的代码如下:

<html>
<head>
<script src="file:///D:/OtherWork/javascript/vue/vue.js"></script>
</head>
<body>
<div id="app">
<Dial style="width: 120px; height: 120px;" v-model="src_value"></Dial>
</div>
<script>
Vue.component('Dial', {
template: '<canvas ref="dial_canvas"></canvas>',
data: function () {
return {
value: 0,
centre: { x: 0, y: 0 },
canvas: null
}
},
props: ['value'],
mounted: function() {
console.log(this.$refs.dial_canvas);
},
render: function() {
// TODO: Draw on canvas here.
}
});
var app = new Vue({
el: '#app',
data: {
src_value: -1
},
created() {
}
});
</script>
</body>
</html>

问题原因与解决方法

兄弟,你这问题其实踩了两个关键的坑:

1. 同名的props和data变量冲突

你在组件里同时声明了props: ['value']和data里的value:0,这俩重名了!Vue里组件的props和data绝对不能用相同的名字,会直接导致变量覆盖,还会打乱组件的响应式逻辑,这是第一个隐患。

2. render函数直接覆盖了template模板

Vue组件里如果同时写了template和render函数,render函数会优先生效!你现在的render函数只留了个TODO注释,根本没有渲染任何元素,所以页面上其实完全没生成canvas标签,自然$refs.dial_canvas和$el都是undefined了。

修正后的代码

根据你的需求(静态文件部署、组件可复用),我给你调整了代码,直接解决这两个问题:

<html>
<head>
<script src="file:///D:/OtherWork/javascript/vue/vue.js"></script>
</head>
<body>
<div id="app">
<Dial style="width: 120px; height: 120px;" v-model="src_value"></Dial>
</div>
<script>
Vue.component('Dial', {
template: '<canvas ref="dial_canvas"></canvas>',
data: function () {
return {
// 把data里的value改成localValue,避免和props的value冲突
localValue: 0,
centre: { x: 0, y: 0 },
canvas: null
}
},
// 实现v-model必须的props和事件逻辑
props: ['value'],
watch: {
// 监听props的value变化,同步到本地变量并触发重绘
value(newVal) {
this.localValue = newVal;
this.drawDial();
}
},
mounted: function() {
this.canvas = this.$refs.dial_canvas;
// 给canvas设置实际宽高(避免样式宽高导致绘制模糊)
this.canvas.width = 120;
this.canvas.height = 120;
this.centre.x = this.canvas.width / 2;
this.centre.y = this.canvas.height / 2;
// 现在可以正常拿到canvas了,打印看看
console.log(this.canvas);
// 初始化绘制表盘
this.drawDial();
},
methods: {
drawDial() {
if (!this.canvas) return;
const ctx = this.canvas.getContext('2d');
// 清空画布
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// 绘制表盘外圈
ctx.beginPath();
ctx.arc(this.centre.x, this.centre.y, 50, 0, Math.PI * 2);
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
ctx.stroke();
// 根据value绘制指针(这里做简单示例,取值范围0-100)
const normalizedValue = Math.max(0, Math.min(100, this.localValue));
const angle = (normalizedValue / 100) * Math.PI * 2 - Math.PI / 2;
ctx.beginPath();
ctx.moveTo(this.centre.x, this.centre.y);
ctx.lineTo(
this.centre.x + Math.cos(angle) * 40,
this.centre.y + Math.sin(angle) * 40
);
ctx.strokeStyle = '#f00';
ctx.lineWidth = 3;
ctx.stroke();
}
}
// 删掉无效的render函数,让template正常生效;如果一定要用render,可以看下面的补充
});
var app = new Vue({
el: '#app',
data: {
src_value: 50 // 改成正数方便看效果
},
created() {
}
});
</script>
</body>
</html>

关键调整点说明:

  • 把data里的value改成localValue,避免和props的value重名,同时通过watch监听props变化,同步数据并触发重绘。
  • 删除了无效的render函数,让template里的canvas正常渲染,这样mounted里就能顺利拿到$refs.dial_canvas了。
  • 给canvas设置了实际宽高,避免样式宽高导致绘制模糊的问题。
  • 添加了drawDial方法统一管理绘制逻辑,初始化和数据变化时都能调用。

如果之后你确实想用render函数代替template,可以这样写(替换掉原来的template):

render(h) {
return h('canvas', {
ref: 'dial_canvas',
attrs: { width: 120, height: 120 }
});
}

这样也能正确生成带ref的canvas元素,mounted里同样能拿到refs。

现在运行代码,你应该就能在控制台看到canvas元素,而且表盘也能正常渲染了~


内容的提问来源于stack exchange,提问作者user526477

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:48:32