备案 控制台
学习
实践
活动
专区
工具
TVP
写文章
专栏首页 Web Front End 一次useEffect引发浏览器执行机制的思考
1 0

海报分享

一次useEffect引发浏览器执行机制的思考

抛出"问题"

我们先来阐述阐述问题,今儿在写一个有关于新手指引的公用组件,类似于这样的形式:

我相信大家首先想到的思路就是在 useEffect 中通过 getBoundingClientRect() 获得对应传入元素( id )的位置,然后通过定位增加一个类似的弹窗效果。

当我天真的以为这样就可以实现它的时,我碰到了一个"无从下手"解决的问题。

useEffect 中获取 getBoundingClientRect() 的值是随机的?

随机的???作为一个基本的程序员,随机的代码执行结果,这我怎么能够接受呢!

我们来看看简化后的代码:

"问题"代码

// 代码已经是很简化的版本了 仅仅保留了核心的内容
import React, { useEffect } from 'react'
import './_index.scss'
const GuideBeta = () => {
  useEffect(() => {
    console.log(document.getElementById('step1'))
    console.log(document.getElementById('step1')?.getBoundingClientRect())
  }, [])
  return (
      <div className='beta'>
        <div id='step1'>
          <div>第一个指引</div>
        <div id='step2'>
          <div>第二个指引</div>
export { GuideBeta }
复制代码

上面代码其实很简单,渲染两个 id step1 step2 的元素,然后在 useEffect() 之中去打印获取 id step1 的元素。

差不多页面渲染出来就是这个样子:

输出结果

这个是正常的输出结果:

当时当我们尝试多刷新几次页面来看看打印结果:

也许你会奇怪是不是我代码写的有问题,这里先卖个小关子 两次不同的打印结果,产生的原因和业务代码没有任何关系

要搞清楚这个问题,我们需要从一些基础的理论知识来层层递进。

血与泪的教训,我 checked 了我的代码整整一早上...

浏览器加载机制

关于浏览器加载机制其实我相信大家已经老生常谈了,这里我结合上边两次不同打印的原理来稍微聊聊对应的机制:

js 执行浏览器会被 js 引擎"霸占",从而导致渲染进程无法执行阻塞 DomTree 的渲染的,那么 Css 呢? css 加载是否会阻塞 Dom Tree 的渲染呢?

让我们带着这个问题来谈谈 css 是否会阻塞 Dom Tree 的构建。

css 加载是否会阻塞 Dom Tree 的渲染和解析

验证 css 加载和 Dom Tree 的关系

我们尝试先来看看这端代码:

<!DOCTYPE html>
<html lang="en">
  <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>
    #h1 {
      color: blue;
  </style>
  <script>
    setTimeout(() => {
      const h1 = document.getElementById('h1')
      console.log(h1)
    }, 0)
  </script>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
    integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
  <h1 id="h1">
    大大的标题
</body>
</html>
复制代码

代码其实很简单,就是在 js 脚本中定时器中获取 h1 标签。之后引入了 bootstrap 样式库。

注意:我们需要将浏览器中"网络"限制为 SLOW 3G 进行测试。

通过上边的表现,我们可以看到当页面加载中。 js 脚本中的 setTimeout 已经成功的在控制台打印出来了 h1 标签对应的元素。

也就是说 css 还未加载完成,我们就已经可以获取到对应的 Dom ,

所以 css 加载并不会阻塞 Dom Tree 的构建。

但是同时注意到,当 css 文件加载完成后页面才会渲染出来蓝色的 大大的标题 ,也就是说 css 文件加载完成后,页面才会进行渲染。

此时我们可以得知 css 的加载是会阻塞 Render Tree 的渲染的,你可以暂时理解成 Render Tree Dom Tree ,之后我们会在后边详细讲解。

css 对于 Dom Tree 结论

我们来谈谈关于 css 加载的结论:

  1. css 加载并不会阻塞 Dom Tree 的构建,因为 css 还未加载完时我们已经可以获取到对应的 h1 标签了。
  2. css 加载会阻塞 Dom Tree 的渲染,只有当 css 加载完成后页面才会渲染出蓝色的 大大的标题

css 加载对于 js 的影响

那么 css 加载对于 js 的是否有影响呢?废话不多说我们来看代码:

css 加载对于 js 验证

<!DOCTYPE html>
<html lang="en">
  <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>
  <script>
    const now = window.now = Date.now()
    console.log('css加载之前', now)
  </script>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
    integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
  <script>
    const scriptExec = Date.now() - window.now + 'ms'
    console.log('css加载完成')
    console.log('间隔:' + scriptExec)
  </script>
</head>
  <h1 id="h1">
    大大的标题
</body>