Vue3类型错误:template ref.value is null

回答 5 浏览 8376 2021-01-16

我怎样才能清除控制台中的以下错误。

enter image description here

Uncaught TypeError: ref.value is null

这个错误只出现在调整大小的事件中。每次我调整窗口大小时,我都会渲染图表。所以错误信息一次又一次地出现。文档显示,模板ref也被初始化为空值(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>

我怎样才能清理我的控制台,使错误信息不再出现?谢谢。

wittgenstein 提问于2021-01-16
一次onMounted,然后我为每次调整大小添加一个事件监听器,去噪时间为250ms。wittgenstein 2021-01-16
不,脚本设置在我的项目中至关重要。没有它就不能做。wittgenstein 2021-01-16
5 个回答
#1楼
得票数 23

我有点晚了,但我在Vue 3也面临同样的问题。我只是通过返回引用来解决这个问题。

<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>
Dony 提问于2021-04-30
你好,谢谢你的回答,但是它和Composition API的文档中说的一样。很遗憾,我使用的是实验性的script setup版本。wittgenstein 2021-04-30
这对我来说也解决了问题!。Tor 2021-10-01
当把脚本设置与正在获取的数据结合起来使用时,我不得不等到数据可用的时候。也就是说,我只在ref不再是空的时候才访问它。B Bau 2022-11-03
#2楼
得票数 7

如果你看到的yourRef.valuenull,那么请确保你试图获得ref的元素没有隐藏在一个假的v-if下。Vue不会将ref实例化,直到该元素被实际需要。

ubershmekel 提问于2022-04-23
谢谢,当元素的值似乎不一致时,我感到困惑Aditya Kresna Permana 2022-05-03
这就是我遇到的问题。通过使用:class与Bootstraps d-none和d-block,而不是使用v-if,解决了这个问题。Axel Kennedal 2022-10-27
#3楼 已采纳
得票数 4

选项A

try...catch包住它

选项2

使用watch的方式


我发现最好的方法是使用watch

下面是一个可以在多个组件之间重复使用的函数的例子。我们可以定义一个生成画布引用的函数,然后将其传递给组件 - 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>元素。我们还可以使用该函数返回的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>

Daniel 提问于2021-01-18
Daniel 修改于2021-01-18
谢谢,它看起来真的很有希望。我将给它一个机会。如果要花一点时间,我很抱歉。wittgenstein 2021-01-19
#4楼
得票数 1

我也有点晚了,但考虑到这是为Vue 3准备的,而且还是2021年,这里是我为那些没有使用Composition API的人准备的解决方案。

在其中一个子组件中,我有这样的东西。

<div ref='popupMenu'>
... some content here ...
</div>

正如预期的那样,this.$refs.popupMenu被设置在mountedactivated的回调中,然而,不管什么原因,当我试图从一个窗口滚动监听器接近它时,它被设置为null。我已经console.log了整个组件,看看发生了什么事,我可以看到$refs.popupMenunull

不知道是我做了什么,还是我误解了当你的父组件也在对它的子组件进行重新渲染时,挂载/激活是如何工作的,但对我来说,解决的办法是:

Template

<div :ref='storeReference'>
.... popup content here ...
</div>

Data

data() {
    popupMenu: null
}

Methods

methods: {
    storeReference(e) {
        if(e) {
            this.popupMenu = e;
        }
    }
    clearReference(e) {
        this.popupMenu = null;
    }
}

这将确保我在任何时候都能保持引用,在我的案例中效果很好。

当控制是unmounteddeactivated时,会有一个方法clearReference,可以设置this.popupMenu = null,这样就不会把它保留在内存中。

Siniša 提问于2021-12-17
#5楼
得票数 1

我为这个问题伤透了脑筋,但基本上,当使用<script setup>时,被暴露的组件的公共实例(当从模板 refs 检索时)不会暴露里面声明的任何绑定关系。

如果你想让它们曝光,请使用defineExpose()

<script setup>
    import { ref } from 'vue'

    const a = 1
    const b = ref(2)

    defineExpose({
        a,
        b
    })
</script>

它是一个编译器宏,所以你不需要导入它。

Mav 提问于2022-10-31
Mav 修改于2022-10-31