为什么我必须在使用 ChartJS 之前创建我的 VueJS 应用程序?

Why must I create my VueJS application before using ChartJS?

我有这个非常简单的页面,可以正常工作:

<!DOCTYPE html>
<html lang="en">

<head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@3.7.0/dist/chart.js"></script>
</head>

<body>
    <main id="vue-app">
        <p>{{ name }}</p>
        <canvas id="chart-flow-rate"></canvas>
    </main>
</body>

<script>    
    // Start VueJS
    const Application = {
        data() {
            return {
                name: "My Chart"
            };
        }
    }
    vm = Vue.createApp(Application).mount('#vue-app');

    // Use ChartJS
    const myChart = new Chart('chart-flow-rate', {
        type: 'bar',
        data: {
            labels: ['4', '2'],
            datasets: [{
                data: [4, 2],
            }]
        }
    });
</script>

但是,如果我反转 JS 块以使用 ChartJS firstthen 创建 VueJS 应用程序,页面不再有效:图表未显示。

为什么?

我注意到我可以稍微更改 HTML 的 body 以便能够在 VueJS 之前使用 ChartJS:

<body>
    <main>
        <p id="vue-app">{{ name }}</p>
        <canvas id="chart-flow-rate"></canvas>
    </main>
</body>

再次:为什么

非常感谢! :)

这是因为 VueJS 使用虚拟 DOM 并且它会批量更新实际的 DOM:您提供给您的应用程序的是一个内联模板,VueJS 将解析、插入和重写该模板它到 DOM。因此,如果您首先初始化 ChartJS,它将丢失对 DOM 元素的引用(它要么因为与 VueJS 的竞争条件而消失,要么在 Vue 计算虚拟 DOM 和将它输出到实际的 DOM).

更改标记起作用的原因是因为现在 ChartJS 用来挂载和呈现图表的元素不再被 VueJS 删除或操作,因为它不再是 Vue 应用程序的一部分,而是驻留在它之外常规 DOM.

事实上,最简约的解决方案是在 VueJS 应用程序的 mounted 挂钩中简单地实例化 ChartJS,然后等待 DOM 准备就绪,即:

mounted: async function() {
    // Wait for the DOM to be rendered after next tick
    await this.$nextTick();

    // Use ChartJS with the element that now exists in the DOM
    const myChart = new Chart('chart-flow-rate', {
        type: 'bar',
        data: {
            labels: ['4', '2'],
            datasets: [{
                data: [4, 2],
            }]
        }
    });
}

事实上,我会更进一步,恳请您避免使用DOM查询方法并利用ref attribute and the $refs instance property。在您的模板中,更新您的标记以使用 refs:

<canvas ref="chart"></canvas>

由于 ChartJS 接受一个元素作为第一个参数,您可以简单地使用 this.$refs.canvas:

访问该元素
const myChart = new Chart(this.$refs.chart, { ... })

见下文proof-of-concept:

// Start VueJS
const Application = {
  data() {
    return {
      name: "My Chart"
    };
  },
  mounted: async function() {
    await this.$nextTick();

    // Use ChartJS
    const myChart = new Chart(this.$refs.chart, {
      type: 'bar',
      data: {
        labels: ['4', '2'],
        datasets: [{
          data: [4, 2],
        }]
      }
    });
  }
}
vm = Vue.createApp(Application).mount('#vue-app');
<script src="https://unpkg.com/vue@next"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.7.0/dist/chart.js"></script>
<main id="vue-app">
  <p>{{ name }}</p>
  <canvas ref="chart"></canvas>
</main>