Vue3 TypeError: template ref.value is null
Vue3 TypeError: template ref.value is null
如何从以下错误中清除我的控制台:
TypeError: ref.value is null
错误仅在调整大小事件中出现。每次我调整 window 的大小时,我都会渲染图表。所以错误信息一次又一次出现。文档显示模板引用也被初始化为空值 (Source)。所以我必须在初始化之后做一些事情,对吗?
这是我的代码:
<template>
<canvas
ref="chartRef"
/>
</template>
<script setup>
// ...
// on resize
export const chartRef = ref(null)
export function createChart () {
const ctx = chartRef.value.getContext('2d')
if (ctx !== null) { // fix me
getDimensions()
drawChart(ctx)
}
}
// ...
</script>
如何清理我的控制台以便不再显示错误消息?谢谢
选项A
将其包装在 try...catch
中
选项 2
使用 watch
我发现最好的方法是使用 watch
这是一个可以在多个组件之间重复使用的函数示例。我们可以定义一个函数来生成 canvas 引用,然后可以将其传递给组件 - canvasRef
.
const withCanvasRef = () => {
let onMountCallback = null;
const onMount = callback => {
onMountCallback = callback;
};
const canvasRef = ref(null);
watch(canvasRef, (element, prevElement) => {
if (element instanceof HTMLCanvasElement) {
canvasRef.value = element;
if (onMountCallback && prevElement === null) onMountCallback(canvasRef);
} else {
ctxRef.value = null;
}
});
return {
canvasRef,
onMount
};
};
然后我们可以获取组件中的canvasRef
并将其传递给<canvas>
元素。我们还可以使用函数 returns 处理初始渲染的 onMounted
挂钩。
app.component("my-line-chart", {
setup: props => {
const { canvasRef, onMount } = withCanvasRef();
const draw = () => {
// stuff here,
// use a check for canvasRef.value if you have conditional rendering
};
// on resize
window.addEventListener("resize", () => draw());
// on canvas mount
onMount(() => draw());
return { canvasRef };
},
template: `<div><canvas ref="canvasRef"/></div>`
});
请参阅示例,例如显示此操作的示例。希望您能看到使用 Composition API 作为更好的代码重用和组织解决方案的好处。 (尽管它的某些方面看起来有点费力,比如必须手动为道具定义手表)
const app = Vue.createApp({
setup() {
const someData = Vue.ref(null);
let t = null;
const numPts = 20;
const generateData = () => {
const d = [];
for (let i = 0; i < numPts; i++) {
d.push(Math.random());
}
if (someData.value == null) {
someData.value = [...d];
} else {
const ref = [...someData.value];
let nMax = 80;
let n = nMax;
t !== null && clearInterval(t);
t = setInterval(() => {
n = n -= 1;
n <= 0 && clearInterval(t);
const d2 = [];
for (let i = 0; i < numPts; i++) {
//d2.push(lerp(d[i],ref[i], n/nMax))
d2.push(ease(d[i], ref[i], n / nMax));
}
someData.value = [...d2];
}, 5);
}
};
generateData();
return { someData, generateData };
}
});
const withCanvasRef = () => {
let onMountCallback = null;
const onMount = callback => {
onMountCallback = callback;
};
const canvasRef = Vue.ref(null);
Vue.watch(canvasRef, (element, prevElement) => {
if (element instanceof HTMLCanvasElement) {
canvasRef.value = element;
if (onMountCallback && prevElement === null) onMountCallback(canvasRef);
} else {
ctxRef.value = null;
}
});
return {
canvasRef,
onMount
};
};
const drawBarGraph = (canvas, data) => {
const width = canvas.width;
const height = Math.min(window.innerHeight, 200);
const ctx = canvas.getContext("2d");
const col1 = [229, 176, 84];
const col2 = [202, 78, 106];
const len = data.length;
const mx = 10;
const my = 10;
const p = 4;
const bw = (width - mx * 2) / len;
const x = i => bw * i + p / 2 + mx;
const w = () => bw - p;
const h = num => (height - my * 2) * num;
const y = num => (height - my * 2) * (1 - num) + my;
const col = i => {
const r = lerp(col1[0], col2[0], i / len);
const g = lerp(col1[1], col2[1], i / len);
const b = lerp(col1[2], col2[2], i / len);
return `rgb(${[r, g, b]})`;
};
data.forEach((num, i) => {
ctx.fillStyle = col(i);
ctx.fillRect(x(i), y(num), w(), h(num));
});
};
const drawLineGraph = (canvas, data) => {
const width = canvas.width;
const height = Math.min(window.innerHeight, 200);
const ctx = canvas.getContext("2d");
const col1 = [229, 176, 84];
const col2 = [202, 78, 106];
const len = data.length;
const mx = 10;
const my = 10;
const p = 4;
const bw = (width - mx * 2) / len;
const x = i => bw * i + p / 2 + mx + bw / 2;
const y = num => (height - my * 2) * (1 - num) + my;
const r = 2;
const col = i => {
const r = lerp(col1[0], col2[0], i / len);
const g = lerp(col1[1], col2[1], i / len);
const b = lerp(col1[2], col2[2], i / len);
return `rgb(${[r, g, b]})`;
};
ctx.lineWidth = 0.2;
ctx.strokeStyle = "black";
ctx.beginPath();
data.forEach((num, i) => {
i == 0 && ctx.moveTo(x(i), y(num));
i > 0 && ctx.lineTo(x(i), y(num));
});
ctx.stroke();
ctx.closePath();
data.forEach((num, i) => {
ctx.beginPath();
ctx.fillStyle = col(i);
ctx.arc(x(i), y(num), r, 0, 2 * Math.PI);
ctx.fill();
});
};
const drawSomething = canvas => {
canvas.width = window.innerWidth / 2 - 5;
canvas.height = Math.min(window.innerHeight, 200);
const ctx = canvas.getContext("2d");
ctx.fillStyle = "rgb(255 241 236)";
ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);
};
app.component("my-bar-chart", {
props: ["data"],
setup: props => {
const { canvasRef, onMount } = withCanvasRef();
const draw = () => {
if (canvasRef.value) {
drawSomething(canvasRef.value);
drawBarGraph(canvasRef.value, props.data);
}
};
// on resize
window.addEventListener("resize", () => draw());
// on data change
Vue.watch(
() => props.data,
() => draw()
);
// on canvas mount
onMount(() => draw());
return { canvasRef };
},
template: `<div><canvas ref="canvasRef"/></div>`
});
app.component("my-line-chart", {
props: ["data"],
setup: props => {
const { canvasRef, onMount } = withCanvasRef();
const draw = () => {
if (canvasRef.value) {
drawSomething(canvasRef.value);
drawLineGraph(canvasRef.value, props.data);
}
};
// on resize
window.addEventListener("resize", () => draw());
// on data change
Vue.watch(
() => props.data,
() => draw()
);
// on canvas mount
onMount(() => draw());
return { canvasRef };
},
template: `<div><canvas ref="canvasRef"/></div>`
});
app.mount("#app");
const lerp = (start, end, amt) => (1 - amt) * start + amt * end;
const ease = (start, end, amt) => {
return lerp(start, end, Math.sin(amt * Math.PI * 0.5));
};
body {
margin: 0;
padding: 0;
overflow: hidden;
}
.chart {
display: inline-block;
margin-right: 4px;
}
<script src="https://unpkg.com/vue@next/dist/vue.global.prod.js"></script>
<div id="app">
<button @click="generateData">Scramble</button>
<div>
<my-bar-chart class="chart" :data="someData"></my-bar-chart>
<my-line-chart class="chart" :data="someData"></my-line-chart>
</div>
</div>
我有点晚了,但我在 Vue 3 中遇到了同样的问题。我通过返回引用解决了 juste :
<template>
<input ref="myinput">
</template>
<script>
import { onMounted, ref } from 'vue'
export default {
setup() {
const myinput = ref(null) // Assign dom object reference to "myinput" variable
onMounted(() => {
console.log(myinput.value) // Log a DOM object in console
})
return { myinput } // WILL NOT WORK WITHOUT THIS
}
}
</script>
我也有点晚了,但考虑到它是针对 Vue 3 的,而且现在还是 2021 年,这是我为那些不使用 Composition API
:
的人提供的解决方案
在其中一个子组件中,我有这样的东西:
<div ref='popupMenu'>
... some content here ...
</div>
正如预期的那样,this.$refs.popupMenu
是在 mounted
和 activated
回调中设置的,但是,无论出于何种原因,当我试图从 window 滚动侦听器接近它时, 它被设置为 null
。我安慰了整个组件以查看发生了什么,我可以看到 $refs.popupMenu
是 null
.
不确定这是不是我做的,或者我对 mount/activate 在父组件也重新渲染它的子组件时如何工作的误解,但对我有用的是:
模板
<div :ref='storeReference'>
.... popup content here ...
</div>
数据
data() {
popupMenu: null
}
方法
methods: {
storeReference(e) {
if(e) {
this.popupMenu = e;
}
}
clearReference(e) {
this.popupMenu = null;
}
}
这将确保我始终保留参考资料,并且在我的案例中效果很好。
当控件为 unmounted
或 deactivated
时,将有一个方法 clearReference
将设置 this.popupMenu = null
,因此它不会将其保留在内存中。
如果您将 yourRef.value
视为 null
,请确保您尝试获取 ref
的元素未隐藏在错误的 [=13] 下=].在实际需要该元素之前,Vue 不会实例化 ref
。
如何从以下错误中清除我的控制台:
TypeError: ref.value is null
错误仅在调整大小事件中出现。每次我调整 window 的大小时,我都会渲染图表。所以错误信息一次又一次出现。文档显示模板引用也被初始化为空值 (Source)。所以我必须在初始化之后做一些事情,对吗?
这是我的代码:
<template>
<canvas
ref="chartRef"
/>
</template>
<script setup>
// ...
// on resize
export const chartRef = ref(null)
export function createChart () {
const ctx = chartRef.value.getContext('2d')
if (ctx !== null) { // fix me
getDimensions()
drawChart(ctx)
}
}
// ...
</script>
如何清理我的控制台以便不再显示错误消息?谢谢
选项A
将其包装在 try...catch
选项 2
使用 watch
我发现最好的方法是使用 watch
这是一个可以在多个组件之间重复使用的函数示例。我们可以定义一个函数来生成 canvas 引用,然后可以将其传递给组件 - canvasRef
.
const withCanvasRef = () => {
let onMountCallback = null;
const onMount = callback => {
onMountCallback = callback;
};
const canvasRef = ref(null);
watch(canvasRef, (element, prevElement) => {
if (element instanceof HTMLCanvasElement) {
canvasRef.value = element;
if (onMountCallback && prevElement === null) onMountCallback(canvasRef);
} else {
ctxRef.value = null;
}
});
return {
canvasRef,
onMount
};
};
然后我们可以获取组件中的canvasRef
并将其传递给<canvas>
元素。我们还可以使用函数 returns 处理初始渲染的 onMounted
挂钩。
app.component("my-line-chart", {
setup: props => {
const { canvasRef, onMount } = withCanvasRef();
const draw = () => {
// stuff here,
// use a check for canvasRef.value if you have conditional rendering
};
// on resize
window.addEventListener("resize", () => draw());
// on canvas mount
onMount(() => draw());
return { canvasRef };
},
template: `<div><canvas ref="canvasRef"/></div>`
});
请参阅示例,例如显示此操作的示例。希望您能看到使用 Composition API 作为更好的代码重用和组织解决方案的好处。 (尽管它的某些方面看起来有点费力,比如必须手动为道具定义手表)
const app = Vue.createApp({
setup() {
const someData = Vue.ref(null);
let t = null;
const numPts = 20;
const generateData = () => {
const d = [];
for (let i = 0; i < numPts; i++) {
d.push(Math.random());
}
if (someData.value == null) {
someData.value = [...d];
} else {
const ref = [...someData.value];
let nMax = 80;
let n = nMax;
t !== null && clearInterval(t);
t = setInterval(() => {
n = n -= 1;
n <= 0 && clearInterval(t);
const d2 = [];
for (let i = 0; i < numPts; i++) {
//d2.push(lerp(d[i],ref[i], n/nMax))
d2.push(ease(d[i], ref[i], n / nMax));
}
someData.value = [...d2];
}, 5);
}
};
generateData();
return { someData, generateData };
}
});
const withCanvasRef = () => {
let onMountCallback = null;
const onMount = callback => {
onMountCallback = callback;
};
const canvasRef = Vue.ref(null);
Vue.watch(canvasRef, (element, prevElement) => {
if (element instanceof HTMLCanvasElement) {
canvasRef.value = element;
if (onMountCallback && prevElement === null) onMountCallback(canvasRef);
} else {
ctxRef.value = null;
}
});
return {
canvasRef,
onMount
};
};
const drawBarGraph = (canvas, data) => {
const width = canvas.width;
const height = Math.min(window.innerHeight, 200);
const ctx = canvas.getContext("2d");
const col1 = [229, 176, 84];
const col2 = [202, 78, 106];
const len = data.length;
const mx = 10;
const my = 10;
const p = 4;
const bw = (width - mx * 2) / len;
const x = i => bw * i + p / 2 + mx;
const w = () => bw - p;
const h = num => (height - my * 2) * num;
const y = num => (height - my * 2) * (1 - num) + my;
const col = i => {
const r = lerp(col1[0], col2[0], i / len);
const g = lerp(col1[1], col2[1], i / len);
const b = lerp(col1[2], col2[2], i / len);
return `rgb(${[r, g, b]})`;
};
data.forEach((num, i) => {
ctx.fillStyle = col(i);
ctx.fillRect(x(i), y(num), w(), h(num));
});
};
const drawLineGraph = (canvas, data) => {
const width = canvas.width;
const height = Math.min(window.innerHeight, 200);
const ctx = canvas.getContext("2d");
const col1 = [229, 176, 84];
const col2 = [202, 78, 106];
const len = data.length;
const mx = 10;
const my = 10;
const p = 4;
const bw = (width - mx * 2) / len;
const x = i => bw * i + p / 2 + mx + bw / 2;
const y = num => (height - my * 2) * (1 - num) + my;
const r = 2;
const col = i => {
const r = lerp(col1[0], col2[0], i / len);
const g = lerp(col1[1], col2[1], i / len);
const b = lerp(col1[2], col2[2], i / len);
return `rgb(${[r, g, b]})`;
};
ctx.lineWidth = 0.2;
ctx.strokeStyle = "black";
ctx.beginPath();
data.forEach((num, i) => {
i == 0 && ctx.moveTo(x(i), y(num));
i > 0 && ctx.lineTo(x(i), y(num));
});
ctx.stroke();
ctx.closePath();
data.forEach((num, i) => {
ctx.beginPath();
ctx.fillStyle = col(i);
ctx.arc(x(i), y(num), r, 0, 2 * Math.PI);
ctx.fill();
});
};
const drawSomething = canvas => {
canvas.width = window.innerWidth / 2 - 5;
canvas.height = Math.min(window.innerHeight, 200);
const ctx = canvas.getContext("2d");
ctx.fillStyle = "rgb(255 241 236)";
ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);
};
app.component("my-bar-chart", {
props: ["data"],
setup: props => {
const { canvasRef, onMount } = withCanvasRef();
const draw = () => {
if (canvasRef.value) {
drawSomething(canvasRef.value);
drawBarGraph(canvasRef.value, props.data);
}
};
// on resize
window.addEventListener("resize", () => draw());
// on data change
Vue.watch(
() => props.data,
() => draw()
);
// on canvas mount
onMount(() => draw());
return { canvasRef };
},
template: `<div><canvas ref="canvasRef"/></div>`
});
app.component("my-line-chart", {
props: ["data"],
setup: props => {
const { canvasRef, onMount } = withCanvasRef();
const draw = () => {
if (canvasRef.value) {
drawSomething(canvasRef.value);
drawLineGraph(canvasRef.value, props.data);
}
};
// on resize
window.addEventListener("resize", () => draw());
// on data change
Vue.watch(
() => props.data,
() => draw()
);
// on canvas mount
onMount(() => draw());
return { canvasRef };
},
template: `<div><canvas ref="canvasRef"/></div>`
});
app.mount("#app");
const lerp = (start, end, amt) => (1 - amt) * start + amt * end;
const ease = (start, end, amt) => {
return lerp(start, end, Math.sin(amt * Math.PI * 0.5));
};
body {
margin: 0;
padding: 0;
overflow: hidden;
}
.chart {
display: inline-block;
margin-right: 4px;
}
<script src="https://unpkg.com/vue@next/dist/vue.global.prod.js"></script>
<div id="app">
<button @click="generateData">Scramble</button>
<div>
<my-bar-chart class="chart" :data="someData"></my-bar-chart>
<my-line-chart class="chart" :data="someData"></my-line-chart>
</div>
</div>
我有点晚了,但我在 Vue 3 中遇到了同样的问题。我通过返回引用解决了 juste :
<template>
<input ref="myinput">
</template>
<script>
import { onMounted, ref } from 'vue'
export default {
setup() {
const myinput = ref(null) // Assign dom object reference to "myinput" variable
onMounted(() => {
console.log(myinput.value) // Log a DOM object in console
})
return { myinput } // WILL NOT WORK WITHOUT THIS
}
}
</script>
我也有点晚了,但考虑到它是针对 Vue 3 的,而且现在还是 2021 年,这是我为那些不使用 Composition API
:
在其中一个子组件中,我有这样的东西:
<div ref='popupMenu'>
... some content here ...
</div>
正如预期的那样,this.$refs.popupMenu
是在 mounted
和 activated
回调中设置的,但是,无论出于何种原因,当我试图从 window 滚动侦听器接近它时, 它被设置为 null
。我安慰了整个组件以查看发生了什么,我可以看到 $refs.popupMenu
是 null
.
不确定这是不是我做的,或者我对 mount/activate 在父组件也重新渲染它的子组件时如何工作的误解,但对我有用的是:
模板
<div :ref='storeReference'>
.... popup content here ...
</div>
数据
data() {
popupMenu: null
}
方法
methods: {
storeReference(e) {
if(e) {
this.popupMenu = e;
}
}
clearReference(e) {
this.popupMenu = null;
}
}
这将确保我始终保留参考资料,并且在我的案例中效果很好。
当控件为 unmounted
或 deactivated
时,将有一个方法 clearReference
将设置 this.popupMenu = null
,因此它不会将其保留在内存中。
如果您将 yourRef.value
视为 null
,请确保您尝试获取 ref
的元素未隐藏在错误的 [=13] 下=].在实际需要该元素之前,Vue 不会实例化 ref
。