React的useEffect与useLayoutEffect执行机制剖析

1,726 阅读2分钟

引言

useEffect和useLayoutEffect是React官方推出的两个hooks,都是用来执行副作用的钩子函数,名字类似,功能相近,唯一不同的就是执行的时机有差异,今天这篇文章主要是从这两个钩子函数的执行时机入手,来剖析一下React的运行原理和浏览器的渲染流程。

官方解释

useLayoutEffect其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前, useLayoutEffect 内部的更新计划将被同步刷新,尽可能使用标准的 useEffect 以避免阻塞视觉更新。

简单来讲,就是:useEffect是异步的,useLayoutEffect是同步的,异(同)步是相对于浏览器执行刷新屏幕Task来说的。

眼见为实

下面将通过一个简单的demo示例来说明具体的执行过程,其中React是16.13.1版本,首先是示例代码:


import React, { useState, useEffect, useLayoutEffect } from 'react';

const EffectDemo = () => {
    const [count, setCount] = useState(0);
    useEffect(function useEffectDemo() {
        console.log('useEffect:', count);
    }, [count]);
    useLayoutEffect(function useLayoutEffectDemo() {
        console.log('useLayoutEffect:', count);
    }, [count]);
    return (
        <div>
            <button
                onClick={() => {
                    setCount(count + 1);
                }}
            >click me</button>
        </div>
    );
};

export default EffectDemo;

功能很简单,就不做界面展示,这里主要是看一下浏览器控制台Performance的监控图: 图片描述 通过两个hooks的执行图可以看出,useLayoutEffect发生在页面渲染到屏幕(用户可见)之前,useEffect发生在那之后,中间还经历了DCL,FCP,FMP,LCP阶段,除开DCL(DomContentLoaded)之外,这些指标是RAIL模型衡量页面性能的标准,总的来说,渲染到屏幕的阶段是一个分水岭,那么渲染包含什么呢,还是看图吧: 图片描述 此阶段完成了样式的计算(Recalculate Style)和布局(Layout),紧接着是一个Task,完成Update Layer Tree,Paint,Composite Layers,经过这一系列的任务后,页面最终呈现给用户,可以用一张图来表示浏览器的渲染过程: 图片描述 后面会有相关学习资料,这里就不展开细说了。

模拟运行示例

在深入了解React的运行之前,首先在本地写一个简单的示例,大致模拟文章开始的例子:

<body>
	<div id="app"></div>
	<script type="text/javascript">
		(function iife(){
			function render() {
				var appNode = document.querySelector('#app');
				var textNode = document.createElement('span');
				textNode.id = 'tip';
				textNode.textContent = 'hello';
				appNode.appendChild(textNode);
			}
			function useLayoutEffectDemo() {
				console.log('useLayoutEffectDemo', document.querySelector('#tip'));
			}
			function useEffectDemo() {
				console.log('useEffectDemo');
			}
			render();
			useLayoutEffectDemo();
			setTimeout(useEffectDemo, 0);
		})();
	</script>
</body>

然后启用Performance监控渲染情况: 图片描述

总结一下: 1.首先运行render,完成后立即执行useLayoutEffectDemo函数(虽然已经插入DOM,但是界面还没有渲染出来); 2.注册异步回调函数useEffectDemo,该函数将在0ms过后加入EventLoop中的宏任务队列; 3.页面开始渲染:Recalculate Style->Layout->Update Layer Tree->Paint->Composite Layers->GPU绘制; 4.取出宏任务useEffectDemo,执行回调;

介绍到这里大家应该有了一个基本的认识了,如果想再继续深入了解执行机制,可查看 www.cnblogs.com/fulu/p/1347…