为什么我必须在使用 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 first 和 then 创建 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>
我有这个非常简单的页面,可以正常工作:
<!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 first 和 then 创建 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>