获取DOM踩得一小坑

获取DOM踩得一小坑使用document对象来获取操作DOM是前端同学的基本功了,可用了框架就该按照人家的套路来写代码,不可以想当然,ref应该是最先能想到的

这篇博客灵感也是源于公司的项目,因为我自己之前是写 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>

我想要的效果是这样的(内网截图传不到外网,小糊):

IMG_20220126_163840.jpg

但是我得到的却是这样的(看似只有一个 <lay-bar-graphs> 组件成功渲染)

IMG_20220126_163724.jpg

打开浏览器开发者工具一查发现,<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

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注