这篇博客灵感也是源于公司的项目,因为我自己之前是写 React 比较多,虽然一直记得使用了框架,开发者就不要原生的操作 DOM,但 React 主流脚手架 CRA 在将虚拟 DOM 转换成真实 DOM 的时候,也用的 document.getElementById
加上 React 写起来就很像原生的 JavaScript,所以可能对这条告诫也没有很大的敬畏之心,直到前几天写公司 Vue 栈项目需求的时候出问题了…
1 问题描述
我在项目里复用了一个叫 <lay-bar-graphs>
的组件,该组件里面用到了 echarts。,大致如下:
<div class="template-bewteen">
<lay-bar-graphs :num="1"></lay-bar-graphs>
<lay-bar-graphs :num="2"></lay-bar-graphs>
</div>
组件里面大致是这样的(就一个容器 div
和包含一个 echarts 渲染的 div
):
<template>
<div class="layGraphs-container">
<div
id="layBarGraphs"
ref="layBarGraphs"
style="width:100%;height:100%;"
>
</div>
</div>
</template>
我想要的效果是这样的(内网截图传不到外网,小糊):
但是我得到的却是这样的(看似只有一个 <lay-bar-graphs>
组件成功渲染)
打开浏览器开发者工具一查发现,<lay-bar-graphs>
组件渲染了两次,但 DOM 树结构里面只有一个 <canvas>
(我们都知道 echarts 会把元素渲染成 canvas),真是奇了怪~
2 解决问题
2.1 聚焦问题
接着我们意识到问题出在渲染这里,也通过 console
等手段确定,echarts 确实渲染了两次。
用过 echarts 的小伙伴都会知道,echarts 需要获取 DOM,大致如下,具体可以去看官网示例
import * as echarts from 'echarts';//引入
var chartDom = document.getElementById('main');//获取DOM
var myChart = echarts.init(chartDom);//初始化
var option = {...};//配置
option && myChart.setOption(option);//渲染
所以我们马上把问题聚焦到获取 DOM 上,同时也是瞬间反应过来。
CSS 是全局的,Vue 和 React 等库虽然做到了组件化,但只是通过闭包等手段取巧的模拟组件效果,事实上 组件里面的id
,class
仍然是全局的。这也是为什么现在脚手架会使用 less/sass/css module 等做局部样式隔离的原因。
这个时候问题又来了,项目中是使用了 less
的,像这样 <style scoped lang="less">
,那为什么获取的 DOM 还是同一个呢?我猜测哈!less
只是将选择器隔离在一个组件内,你复用的时候,还是这个组件,所以选择器选择的结果还是一样的。
顺带一提,浏览器自带的 web component
最近经常听到吧,不用框架,浏览器原生的支持组件化,也不需要考虑样式冲突,真不错,期待后续的普及。
2.2 解决方案
在框架中获取 DOM,肯定能想到使用 ref
。那为什么使用 ref
就不会有问题呢?我来回顾一下。
被 ref
标记的元素会成为组件实例的一个属性,组件的每次使用都会创建一个新的实例,这样即使属性名一样,但是他们能够区分谁是谁,这样 echarts 的渲染也就不是同一个元素了。
3 拓展
那么原生 HTML 、JS 是如何复用 echarts 组件的呢?经过和同事以及群里老哥的讨论,我们写了一些代码,大伙可以粘贴自己去试一下~
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style> div{ border: black solid 1px; width: 400px; height: 400px; } .bule { background-color: #00f; width: 3oopx; height: 300px; } .green { background-color: #0f0; width: 3oopx; height: 300px; } </style>
</head>
<body>
<div id="t1"></div><div id="t2"></div>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@5.3.0-rc.1/dist/echarts.min.js"></script>
<script> function fn (color) { let obj = document.createElement('p'); obj.className = color//这种不起作用,因为echarts已经画完了,你才给个高度,晚了 obj.style.width = '300px'//需要及时给 obj.style.height = '300px' var myChart = echarts.init(obj); var option; option = { xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] }, yAxis: { type: 'value' }, series: [ { data: [820, 932, 901, 934, 1290, 1330, 1320], type: 'line', smooth: true } ] }; option && myChart.setOption(option); return obj; } document.getElementById('t1').appendChild( fn('green') ); document.getElementById('t2').appendChild( fn('bule') ); </script>
</body>
</html>
这种方式比较取巧的是,通过 createElement
封装成一个函数,每次执行都新建元素,不存在渲染同一个元素,另外在 Vue 中也可以通过 document.getElementsByClassName
来获取 DOM,因为是一个类数组嘛!复用组件的时候传递一个参数进去,指定渲染哪个 DOM (但肯定用 ref 来的简单)。
今天的文章获取DOM踩得一小坑分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/19162.html