Echarts与Vue3中获取DOM节点可能出现的异常错误
useTemplateRef 的简单介绍
官方:返回一个浅层 ref,其值将与模板中的具有匹配 ref attribute 的元素或组件同步。
参数匹配机制:useTemplateRe的参数需与模板中 ref 属性值必须完全一致
响应式变量类型明确:返回值是一个 浅层 ref对象,其 .value 直接指向绑定的 DOM 元素或组件实例。
useTemplateRef源码浅析
packages/runtime-core/src/helpers/useTemplateRef.ts 文件中
import { type ShallowRef, readonly, shallowRef } from '@vue/reactivity'
import { getCurrentInstance } from '../component'
import { warn } from '../warning'
import { EMPTY_OBJ } from '@vue/shared'
export function useTemplateRef(key) {
// 获取当前 Vue 实例对象。
const i = getCurrentInstance()
// 创建一个浅层的ref对象r,初始值为null。
const r = shallowRef(null)
//如果存在 Vue 实例
if (i) {
// i.refs 默认初始化为 EMPTY_OBJ(空对象)首次使用时动态创建新对象
const refs = i.refs === EMPTY_OBJ ? (i.refs = {}) : i.refs
Object.defineProperty(refs, key, {
enumerable: true,
get: () => r.value,
set: val => (r.value = val),
})
}
return r
}
1.获取当前 Vue 实例对象。
2.创建一个浅层的ref对象r,初始值为null。
3.如果存在 Vue 实例,则获取实例上的refs属性(用于存储模板引用)。
4.使用Object.defineProperty对refs对象的key属性进行拦截:
get拦截:返回r.value,即useTemplateRef返回的ref变量的值。
set拦截:将val赋值给r.value,从而将 DOM 元素或组件实例绑定到ref变量上。
5.返回ref对象r。
下面这段代码看看有啥问题
<template>
<div>
<div class="pic" ref="chartNode"></div>
</div>
</template>
<script setup>
import * as echarts from 'echarts'
import { onMounted, useTemplateRef } from 'vue'
// useTemplateRef接受的是一个字符串,表示你要获取哪一个节点
const divNode = useTemplateRef("chartNode");
console.log(1111, divNode)
// 初始化图表
let chartDom = echarts.init(divNode);
onMounted(() => {
drawCharts()
})
const drawCharts=()=>{
// 指定图表的配置项和数据
let option = {
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月']
},
yAxis: {
type: 'value'
},
tooltip: {
trigger:'axis',
},
series:[
{
data: [1150, 200, 300, 356, 105, 200, 345],
type: 'line'
}
]
};
// 使用刚指定的配置项和数据显示图表。
chartDom.setOption(option);
}
</script>
<style scoped>
.pic{
width: 600px;
height: 380px;
}
</style>
实际情况:this.dom.getContext is not a function
报错信息: Uncaught (in promise) TypeError: this.dom.getContext is not a function
01-echarts引入报错.jpg
为啥会报这个错误呢?
原因:因为在初始化echarts的时候,echarts规定只能传入实际的DOM元素。
此时我们传递的是Ref对象,而不是实际的DOM元素
需要传递 divNode.value。
传递一个实际的DOM元素(divNode.value)
const divNode = useTemplateRef("chartNode");
console.log(1111, divNode)
// 初始化图表,传递实际DOM
let chartDom = echarts.init(divNode.value);
onMounted(() => {
drawCharts()
})
const drawCharts=()=>{
...代码爆出不变
}
为啥还会报错:Error: Initialize failed: invalid dom.
原因在于:let chartDom = echarts.init(divNode.value);这一行代码。
此时divNode.value为null。
为啥是null呢?
这个跟获取时机有关:此时还没有完成绑定哈,所以得到的是null。
什么时候可以正常绑定呢?
第1种:在onMounted中肯定是绑定了,此时divNode.value是一个实际的DOM元素了。
第2种:与它同一级的DOM元素已经渲染完成(其实也是在onMounted)
使用useTemplateRef正常渲染图表
<template>
<div>
<div class="pic" ref="chartNode"></div>
</div>
</template>
<script setup>
import * as echarts from 'echarts'
import { onMounted, useTemplateRef } from 'vue'
let chartDom = null //存储的是 ECharts 实例
// useTemplateRef接受的是一个字符串,表示你要获取哪一个节点
const divNode = useTemplateRef("chartNode");
console.log(222, divNode)
onMounted(() => {
// 在onMounted中初始化图表,可以得到原生的DOM节点
chartDom = echarts.init(divNode.value);
// 调用图表
drawCharts()
})
const drawCharts=()=>{
// 指定图表的配置项和数据
let option = {
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月']
},
yAxis: {
type: 'value'
},
tooltip: {
trigger:'axis',
},
series:[
{
data: [1150, 200, 300, 356, 105, 200, 345],
type: 'line'
}
]
};
// 使用刚指定的配置项和数据显示图表。
chartDom.setOption(option);
}
</script>
<style scoped>
.pic{
width: 600px;
height: 380px;
}
</style>
DOM元素已经渲染完成,useTemplateRef拿到DOM元素
<template>
<div class="art-page">
<div ref="divNode">我是div元素</div>
<button @click="getNodeHandler">我是按钮,获取div节点</button>
</div>
</template>
<script setup>
import { useTemplateRef } from 'vue'
const divNode = useTemplateRef("divNode");
const getNodeHandler= () =>{
/**
* 为啥这里的 divNode.value不是null
* 因为:与它同一级的DOM元素已经渲染完成。
* 也就是说:点击的时候已经渲染完成了。也就完成了DOM的绑定,因此可以拿到DOM元素
*/
if(divNode.value){
divNode.value.innerText = '通过dom来赋值';
}
}
</script>
当然我们除了使用 useTemplateRef 来获取DOM元素
还可以使用 ref 来获取DOM元素
使用 ref 来获取DOM元素
使用ref 来获取DOM元素需要注意的点。
通过ref函数创建,并赋值给与模板中同名的变量。
<div class="pic" ref="chartNode"></div>
<script setup>
// 通过ref函数创建,并赋值给与模板中同名的变量。
const chartNode = ref(null)
</script>
错误的获取DOM节点的方式
<div class="pic" ref="chartNode"></div>
<script setup>
// 这一种是创建了一个响应式的对象。并不是获取DOM节点。
const node = ref('chartNode')
</script>
使用 ref 来获取DOM元素,并渲染echarts
<template>
<div>
<div class="pic" ref="chartNode"></div>
</div>
</template>
<script setup>
import * as echarts from 'echarts'
import { onMounted,ref } from 'vue'
let chartDom = null //存储的是 ECharts 实例
let chartNode = ref() // 通过ref函数创建,并赋值给与模板中同名的变量。
onMounted(() => {
// 在onMounted中初始化图表,可以得到原生的DOM节点
chartDom = echarts.init(chartNode.value);
// 调用图表
drawCharts()
})
const drawCharts=()=>{
// 指定图表的配置项和数据
let option = {
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月']
},
yAxis: {
type: 'value'
},
tooltip: {
trigger:'axis',
},
series:[
{
data: [1150, 200, 300, 356, 105, 200, 345],
type: 'line'
}
]
};
// 使用刚指定的配置项和数据显示图表。
chartDom.setOption(option);
}
</script>
<style scoped>
.pic{
width: 600px;
height: 380px;
}
</style>
存储的是ECharts实例变成响应式数据出现的问题
<template>
<div class="chart-page">
<div class="pic" ref="chartNode"></div>
</div>
</template>
<script setup>
import * as echarts from 'echarts'
import { onMounted,ref, onBeforeUnmount } from 'vue'
// 存储 echarts 实例的变量
et chartDom = ref(null)
let chartNode = ref()
onMounted(() => {
// 存储的是 ECharts 实例此时是一个响应式的
chartDom.value = echarts.init(chartNode.value);
// 调用图表
drawCharts()
// 监听窗口大小变化,重绘图表
window.addEventListener('resize', chartRedraw)
})
onBeforeUnmount(()=>{
window.removeEventListener('resize', chartRedraw)
})
const drawCharts = () => {
let option = {
...配置不变
}
chartDom.value.setOption(option);
}
const chartRedraw = ()=> {
chartDom.value && chartDom.value.resize()
}
</script>
缩放echarts出现:Cannot read properties of undefined (reading 'type')
缩放窗口大小,echarts图表出现报错信息如下:
Cannot read properties of undefined (reading 'type')
原因是:存储的是 ECharts 实例变成了响应式,从而在resize 的时候获取不到。
其实存储 ECharts 实例不应该是一个响应式的数据。
就是一个普通类型的数据就行
解决办法
现在我们知道出现问题的原因。
解决办法就是不让它变成响应式的数据就行。
1:存储的是 ECharts实例不要变成响应式,让其成为普通对象。
2.使用markRaw将它标记为一个对象,使其不会被转换为响应式对象
第1种我们已经使用过了,下面我们演示第2种
Vue3中的markRaw
我的理解是: ****
用于标记一个对象,使其永远不会被转换为响应式对象。返回该对象本身。
该对象即使被包裹在 reactive()、ref()、shallowReactive() 等响应式 API 中
也会保持原始状态,不会触发依赖追踪和视图更新。
官网的解释是:
将一个对象标记为不可以被转化为代理对象。返回该对象本身。
使用markRaw来解决
<script setup>
import { onMounted,ref, onBeforeUnmount, markRaw } from 'vue'
// ...省略其他代码,其他代码不变,使用markRaw包裹
chartDom.value = markRaw(echarts.init(chartNode.value));
// ...省略其他代码,其他代码不变
</script>
切换页面的时候echarts实例会自动销毁嘛?
不会的。需要手动销毁。
在 Vue 中切换路由(页面)时或刷新页面时,ECharts 实例不会自动销毁。
需手动调用 dispose() 方法销毁实例,否则会导致内存泄漏或二次渲染异常。
销毁ECharts实例
let chartDom = echarts.init('echarts容器');
// 销毁实例,避免内存泄漏
onBeforeUnmount(()=>{
chartDom && chartDom.dispose()
})
<script>
解决echarts第二次无法渲染的问题
// 获取存储echarts容器的节点
let chartNode = document.getElementById('chart')
//移除容器上的 _echarts_instance_ 属性
chartNode.removeAttribute('_echarts_instance_')
避免多次重复初始 echarts
通过 echarts.getInstanceByDom() 检查是否已存在实例,避免重复初始化
<template>
<div class="chart-page">
<div class="pic" ref="chartNode"></div>
</div>
</template>
<script setup>
import { ref } from 'vue'
let chartNode = ref()
// echarts.getInstanceByDom的参数是页面中渲染echarts的DOM节点
chartInstance = echarts.getInstanceByDom(chartNode.value);
// 检查是否已存在实例
if (!chartInstance) {
console.log('实例不存在')
}else{
console.log('实例已存在')
}
<script>
Echarts与Vue3中获取DOM节点可能出现的异常错误的更多相关文章
- Vue3 组合式 API 中获取 DOM 节点的问题
模板引用 Vue 提供了许多指令让我们可以直接操作组件的模板.但是在某些情况下,我们仍然需要访问底层 DOM 元素.在模板中添加一个特殊的属性ref就可以得到该元素. 访问模板引用 <scrip ...
- 六、React 键盘事件 表单事件 事件对象以及React中的ref获取dom节点 、React实现类似Vue的双向数据绑定
接:https://www.cnblogs.com/chenxi188/p/11782349.html 事件对象 .键盘事件. 表单事件 .ref获取dom节点.React实现类似vue双向数据绑定 ...
- Vue 双向数据绑定、事件介绍以及ref获取dom节点
vue是一个MVVM的框架 M model V view MVVM model改变会影响视图view,view改变会影响model 双向数据绑定必须在表单里面使用 //我发现在谷歌浏览器翻译后的网页 ...
- Omi框架学习之旅 - 获取DOM节点 及原理说明
虽然绝大部分情况下,开发者不需要去查找获取DOM,但是还是有需要获取DOM的场景,所以Omi提供了方便获取DOM节点的方式. 这是官网的话,但是我一直都需要获取dom,对dom操作,所以omi提供的获 ...
- React对比Vue(03 事件的对比,传递参数对比,事件对象,ref获取DOM节点,表单事件,键盘事件,约束非约束组件等)
import React from 'react'; class Baby extends React.Component { constructor (props) { super(props) t ...
- javascript中获取dom元素的高度和宽度
javascript中获取dom元素高度和宽度的方法如下: 网页可见区域宽: document.body.clientWidth网页可见区域高: document.body.clientHeight网 ...
- ionic2 获取dom节点
ionic2页面上面获取dom节点,可以直接用原生的方法,document.querySelector()等, 但是不建议这样使用,建议使用官方的.就是要在获取的节点上加上#name的属性(相当于ge ...
- vue的数据双向绑定和ref获取dom节点
vue是一个MVVM的框架 业务逻辑代码即js部分是model部分, html是view部分. 当model改变的时候,view也会改变 view 改变是,model也会改变 <template ...
- 原生JS获取DOM 节点到浏览器顶部的距离或者左侧的距离
关于js获取dom 节点到浏览器顶/左部的距离,Jquery里面有封装好的offset().top/offset().left,只到父级的顶/左部距离position().top/position() ...
- javascript中获取dom元素高度和宽度
javascript中获取dom元素高度和宽度的方法如下: 网页可见区域宽: document.body.clientWidth网页可见区域高: document.body.clientHeight网 ...
随机推荐
- IOS中的Context Menu
IOS中的Context Menu 通过长按组件或者3D touch方式,周边全部虚化,弹出一个可操作的菜单,并且菜单之间也可以嵌套 IOS13之后已经弃用UIViewControllerPrevie ...
- 关于 Span 的一切:探索新的 .NET 明星:5. .NET 运行时的处理
.5. NET 运行时会怎么样? 1. Span<T> 是什么? 2. Span<T> 是如何实现的? 3. 什么是 Memory<T>,以及为什么你需要它? 4. ...
- 中电金信:零售经营“新赛道” ——基于手机银行APP专区调研的客群精细化运营分析报告
随着银行业竞争的不断深入及新客户增量日渐"到顶",各家银行的客群竞争逐渐由"跑马圈地"进入"精耕细作"的新阶段,在客群精准化服务方面不断深 ...
- CW信号的正交解调
1.CW信号 CW可以叫做等幅电报,它通过电键控制发信机产生短信号"."(点)和长信号"--"(划),并利用其不同组合表示不同的字符,从而组成单词和句子. ...
- 龙哥量化:通达信macd和kdj跨周期引用导致信号漂移等未来函数详细解释
代写技术指标.量化策略,微信:Long622889 龙哥QQ:591438821 跨周期引用macd就是未来函数,导致信号漂移,简直怕了未来函数,那怎么解决呢,调大参数即可, 例如:收盘价 > ...
- ThreeJs-083D动画系统详解
一.动画原理和应用 three的动画大概就是通过不同时间的关键帧来实现 加载一个手机模型 在这个对象里面,注意后期都是直接通过可视化软件Blender编辑好关键帧就能实现动画,这也是个已经编辑好的动画 ...
- Qt编写地图综合应用26-覆盖物交互
一.前言 百度地图本身提供了非常友好完善的JS函数接口用于添加各种覆盖物,比如标注点.矩形区域.圆形区域.不规则线段.弧形等,基本上涵盖了各种应用场景,官方的文档和示例也是比较完善的,虽然示例用的都是 ...
- 网页端IM通信技术快速入门:短轮询、长轮询、SSE、WebSocket
本文来自"糊糊糊糊糊了"的分享,原题<实时消息推送整理>,有优化和改动. 1.写在前面 对Web端即时通讯技术熟悉的开发者来说,我们回顾网页端IM的底层通信技术,从短轮 ...
- Python格式化字符串字面值 | 被官方文档称之为『漂亮』的输出格式
Python格式化字符串字面值 | 被官方文档称之为『漂亮』的输出格式 本文参考输入输出 - Python 3.7.10 文档.首先声明咱的实验环境. ❯ python --version Pytho ...
- 深入理解ASP.NET Core 管道的工作原理
在 .NET Core 中,管道(Pipeline)是处理 HTTP 请求和响应的中间件组件的有序集合.每个中间件组件都可以对请求进行处理,并将其传递给下一个中间件组件,直到请求到达最终的处理程序.管 ...