likes
comments
collection
share

网上对useEffect、useLayoutEffect的解释真是太差了这篇文章会是讲解useEffect与useLay

作者站长头像
站长
· 阅读数 20

如果你不能将一个事物简单化,那说明你还没触碰到事物的本质。

这句话非常适用于今天要讨论的问题。

这个知识点我之前一直有关注,但是我一直没找到答案,一方面是React官网没解释清楚,另一方面是网上的资料真是太差劲了,99.9%的文章都是一样,相互抄袭,但源头都一样,摘抄自某几位大神,而这几位大神呢,都有一个共同的特点,直接给你展示源码,当时我的思想如下:

emmm,源码... 源码你既然都能读明白,而且读的还那么细,那你为啥不实现一个呢(比如实现一个react,实现一个.vue模版)?或者为啥不把某个知识点可视化出来呢? 后来我想明白了,无非以下3点:

  • 对方找资源的能力比较强,因此他能比你先写出来看似华丽的源码解析。
  • 似懂非懂,但如果能把自己营销出去,这也是一种能力。毕竟代码这东西,你硬读也是可以的,那就走量呗。
  • 或许大神们不屑于写这类文章,观众懂不懂的,优先级靠后,自己知道就行。

回到我们这篇文章,请看下面的代码:

let dom = document.createElement('div');
dom.classList.add('div1');
dom.style.left = '200px';
dom.style.left = '100px';
document.body.appendChild(dom);

css如下:

.div1 {
    width: 150px;
    height: 150px;
    background-color: cornflowerblue;
    box-sizing: border-box;
    position: relative;
    left: 0px;
    transition: all 3s;
    display: flex;
    justify-content: center;
    align-items: center;
}

执行一下上面的这2段代码,给大家2个选项:

  • div直接显示在距离左侧100px的位置,并且无过渡效果;
  • div先从0运动200,再由200px运动到100px,整个过程都有过渡效果;

大家认为哪个选项是正确答案?

答案是第一个。

运行上面的代码后我们会发现,极大多数的情况下,不是说js引擎运行一行代码,浏览器就渲染一行代码的差异。那浏览器什么时候会渲染差异呢?这跟浏览器的事件循环有关,有关事件循环的运行机制,建议大家去chrome for developer或者web.dev平台上找找答案,就不要看国内的文章了,国内的也是看的国外。这个知识点你只能是无限接近,但你绝对不可能得到一个标准答案,因为事件循环是浏览器的内部实现,对外来讲是一个黑盒。

有了上面的认识下,我们看一下这段React代码:

import React, { useRef, useEffect, useLayout } from 'react';

export default function App(){

    let ball1 = useRef(null);
    let ball2 = useRef(null);

    useEffect(
        () => {
            if (ball1.current){
                ball1.current.style.left = `200px`;
            }
        },
        []
    )
    
    useLayoutEffect(
        () => {
            if (ball2.current){
                ball2.current.style.left = `200px`;
            }
        },
        []
    )

    return <>
        <h1>useEffect</h1>
        <div className='ball1' ref={ball1}></div>
        <h1>useLayoutEffect</h1>
        <div className='ball2' ref={ball2}></div>
    </>
}

ball1、ball2的样式如下:

.ball1, .ball2 {
    width: 100px;
    height: 100px;
    box-sizing: border-box;
    background-color: cornflowerblue;
    transition: left 10s;
    position: relative;
    left: 0;
}
.ball1 {
    background-color: green;
}

在执行上面这段代码之前,为了能够明显的看出差异,我们手动调整下cpu的运行效率,如下:

网上对useEffect、useLayoutEffect的解释真是太差了这篇文章会是讲解useEffect与useLay

从英文名字上也能看出来,我们手动将cpu的运行效率调整为原先的1/6。

此时执行一下我们的代码,效果如下:

在mount阶段,为啥会出现这种现象?为啥useEffect会有过渡效果,而useLayoutEffect没有?

上面这个问题本质上是问你,useEffect、useLayoutEffect、appendChild之间的执行顺序。

不管你是否读过源码,如果读过更好,你在useEffect、useLayoutEffect里打个断点,然后在node_modules下的react-dom的包里,全局搜索下appendChild,你会发现,它们三个其实都运行在commitRootImpl函数里。并且代码写法顺序如下:


useEffect();

document.body.appendChild();

useLayoutEffect();

那为啥在这段代码里,useEffect会有过渡效果呢?

我们知道useEffect是异步运行的,在js里,setTimeout、MessageChannel等等是能够实现这个需求的,而这些也是useEffect的底层实现。

我们来看下这段html代码:

<button onclick="changePosition1()">useLayoutEffect</button>
<script>
    function changePosition1(){
        let dom = document.createElement('div');
        dom.appendChild(document.createTextNode('useLayoutEffect'));
        dom.classList.add('div1');
        document.body.append(dom);
        dom.style.left = '100px';
    }
</script>

我们运行下这段代码,点击useLayoutEffect,我们会发现,页面上立即出现了距离左侧100px的div。

网上对useEffect、useLayoutEffect的解释真是太差了这篇文章会是讲解useEffect与useLay

但是如果修改下代码,添加一些耗时操作呢,如下:

function changePosition1(){
    let dom = document.createElement('div');
    dom.appendChild(document.createTextNode('useLayoutEffect'));
    dom.classList.add('div1');
    document.body.append(dom);
    let i = 0;
    while(i < 1000000000){
        i++;
    }
    dom.style.left = '100px';
}

我们运行后发现,虽然点击useLayoutEffect后也出现了距离左侧100px的div,但是明显延迟了,有点卡顿的感觉。

反应过来了吗,这就是useLayoutEffect,append后面的操作都是useLayoutEffect里的callback,如果非要找对标的话,你可以把useLayoutEffect比作requestAnimationFrame,都是会在浏览器执行渲染前,同步去做一些事情。这也是为啥官方不建议你在这里做一些耗时操作的原因,因为useLayoutEffect的执行必然发生在当前事件循环里,这会让主线程占据了太多的时间。

我们再看下useEffect,代码如下:

<style>
.div2 {
   width: 150px;
   height: 150px;
   position: relative;
   left: 0;
   background: green;
}
</style>

<button onclick="changePosition2()">useEffect</button>

function changePosition2(){
    setTimeout(
        function (){
            let i = 0;
            while(i < 1000000000){
                i++;
            }
            dom.style.left = '100px';
        }
    )
    let dom = document.createElement('div');
    dom.appendChild(document.createTextNode('useEffect'));
    dom.classList.add('div2');
    document.body.append(dom);
}

当我们点击useEffect后,页面上会立即出现div,然后div开始向右移动,并伴随过渡效果。

这就是useEffect,它的执行必然不在当前事件循环里。如果非要找对标的话,从渲染与代码执行的顺序角度看的话,useEffectrequestIdleCallback倒是几分相似。

好啦,本期内容到这里也就结束啦,大概率这篇文章不会被平台推荐,因为行文风格是平台不太中意的,如果能够帮到你,真是万分荣幸。

那么,我们下期再见啦,拜拜~~

转载自:https://juejin.cn/post/7403658547718520851
评论
请登录