useEffect和useLayoutEffect的区别

news2024/11/25 14:21:04

使用方式
这两个函数的使用方式其实非常简单,他们都接受一个函数``一个数组,只有在数组里面的值改变的情况下才会再次执行 effect

差异

  • useEffect 是异步执行的,而useLayoutEffect是同步执行的。
  • useEffect 的执行时机是浏览器完成渲染之后,而 useLayoutEffect 的执行时机是浏览器把内容真正渲染到界面之前,和 componentDidMount 等价。

具体表现

我们用一个例子说明

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

function App() {
  const [state, setState] = useState("hello world")

  useEffect(() => {
    let i = 0;
    while(i <= 100000000) {
      i++;
    };
    setState("world hello");
  }, []);

  // useLayoutEffect(() => {
  //   let i = 0;
  //   while(i <= 100000000) {
  //     i++;
  //   };
  //   setState("world hello");
  // }, []);

  return (
    <>
      <div>{state}</div>
    </>
  );
}

export default App;

在这里插入图片描述

而换成 useLayoutEffect 之后闪烁现象就消失了。

·useEffect· 是渲染完之后异步执行的,所以会导致 ·hello world ·先被渲染到了屏幕上,再变成 ·world hello·,就会出现·闪烁现象·。而 useLayoutEffect是渲染之前同步执行的,所以会等它执行完再渲染上去,就避免了闪烁现象。也就是说我们最好把操作 dom 的相关操作放到 useLayouteEffect 中去,避免导致闪烁。

源码剖析
useEffect
首先找到 useEffect 调用的入口

function updateEffect(create, deps) {
  {
    // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
    if ('undefined' !== typeof jest) {
      warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber$1);
    }
  }

  return updateEffectImpl(Update | Passive, Passive$1, create, deps);
}

调用 updateEffectImpl 时传入的 hookEffectTag Passive$1 , 所以我们找一下:Passive$1

然后我们找到是在这里传入了 Passive$1 类型来调用 useEffect

function commitPassiveHookEffects(finishedWork) {
  if ((finishedWork.effectTag & Passive) !== NoEffect) {
    switch (finishedWork.tag) {
      case FunctionComponent:
      case ForwardRef:
      case SimpleMemoComponent:
      case Block:
        {
          // TODO (#17945) We should call all passive destroy functions (for all fibers)
          // before calling any create functions. The current approach only serializes
          // these for a single fiber.
          commitHookEffectListUnmount(Passive$1 | HasEffect, finishedWork);
          commitHookEffectListMount(Passive$1 | HasEffect, finishedWork);
          break;
        }
    }
  }
}

那我们继续顺藤摸瓜找 commitPassiveHookEffects

function flushPassiveEffectsImpl() {
    //...省略
    while (_effect2 !== null) {
      {
        setCurrentFiber(_effect2);
        invokeGuardedCallback(null, commitPassiveHookEffects, null, _effect2);
      }
   }
    //...省略
}

flushPassiveEffectsImpl

function flushPassiveEffects() {
  if (pendingPassiveEffectsRenderPriority !== NoPriority) {
    var priorityLevel = pendingPassiveEffectsRenderPriority > NormalPriority ? NormalPriority : pendingPassiveEffectsRenderPriority;
    pendingPassiveEffectsRenderPriority = NoPriority;
    return runWithPriority$1(priorityLevel, flushPassiveEffectsImpl);
  }
}

再往上一层是commitBeforeMutationEffects,这里面调用flushPassiveEffects的方法是scheduleCallback,这是一个调度操作,是异步执行的。

function commitBeforeMutationEffects{
    //...省略
    if ((effectTag & Passive) !== NoEffect) {
      // If there are passive effects, schedule a callback to flush at
      // the earliest opportunity.
      if (!rootDoesHavePassiveEffects) {
        rootDoesHavePassiveEffects = true;
        scheduleCallback(NormalPriority, function () {
          flushPassiveEffects();
          return null;
        });
      }
    }
    //...省略
}

继续顺着 commitBeforeMutationEffects方法往上找的话,我们可以找到最终调用 useEffect 的地方是 commitRootImpl ,这是我们 commit 阶段会调用的一个函数,所以就是在这里面对 useEffect 进行了调度,在完成渲染工作以后去异步执行了 useEffect

useLayoutEffect

function updateLayoutEffect(create, deps) {
  return updateEffectImpl(Update, Layout, create, deps);
}

这里传进去的 hookEffectTagLayout,那么我们找一下Layout

function commitLifeCycles(finishedRoot, current, finishedWork, committedExpirationTime) {
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent:
    case Block:
      {
        // At this point layout effects have already been destroyed (during mutation phase).
        // This is done to prevent sibling component effects from interfering with each other,
        // e.g. a destroy function in one component should never override a ref set
        // by a create function in another component during the same commit.
        commitHookEffectListMount(Layout | HasEffect, finishedWork);

        return;
      }

    case ClassComponent:
      {
        var instance = finishedWork.stateNode;

        if (finishedWork.effectTag & Update) {
          if (current === null) {
            startPhaseTimer(finishedWork, 'componentDidMount'); // We could update instance props and state here,
            // but instead we rely on them being set during last render.
            // TODO: revisit this when we implement resuming.

            {
              if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {
                if (instance.props !== finishedWork.memoizedProps) {
                  error('Expected %s props to match memoized props before ' + 'componentDidMount. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentName(finishedWork.type) || 'instance');
                }

                if (instance.state !== finishedWork.memoizedState) {
                  error('Expected %s state to match memoized state before ' + 'componentDidMount. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentName(finishedWork.type) || 'instance');
                }
              }
            }

            instance.componentDidMount();
            stopPhaseTimer();
          } else {
            var prevProps = finishedWork.elementType === finishedWork.type ? current.memoizedProps : resolveDefaultProps(finishedWork.type, current.memoizedProps);
            var prevState = current.memoizedState;
            startPhaseTimer(finishedWork, 'componentDidUpdate'); // We could update instance props and state here,
            // but instead we rely on them being set during last render.
            // TODO: revisit this when we implement resuming.

            {
              if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {
                if (instance.props !== finishedWork.memoizedProps) {
                  error('Expected %s props to match memoized props before ' + 'componentDidUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentName(finishedWork.type) || 'instance');
                }

                if (instance.state !== finishedWork.memoizedState) {
                  error('Expected %s state to match memoized state before ' + 'componentDidUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentName(finishedWork.type) || 'instance');
                }
              }
            }

            instance.componentDidUpdate(prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate);
            stopPhaseTimer();
          }
        }
      ...省略
}

而在这里我们可以看到,class 组件的 componentDidMount生命周期也是在这里被调用的,所以其实useLayoutEffect是和componentDidMount等价的。

而一直往上找最后还是会找到 commitRootImpl方法中去,同时在这个过程中并没有找到什么调度的方法,所以 useLayoutEffect会同步执行。

总结

  • 优先使用 useEffect,因为它是异步执行的,不会阻塞渲染
  • 会影响到渲染的操作尽量放到 useLayoutEffect中去,避免出现闪烁问题
  • useLayoutEffect和componentDidMount是等价的,会同步调用,阻塞渲染
  • useLayoutEffect在服务端渲染的时候使用会有一个 warning,因为它可能导致首屏实际内容和服务端渲染出来的内容不一致。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/143878.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

修复被删除的数据库表

1.问题来源 有一天领导让我对比生产数据库表和测试数据库表&#xff0c;要确保表结构&#xff0c;字段类型一致。于是我导出测试环境数据库表的DDL&#xff0c;在导出表的时候有blob和clob的表报错&#xff0c;于是我就想把它给剔除再导出&#xff0c;就这样数据库表被我删掉了…

c++调python踩坑日志

目录 import_array();报错 矩阵互相转换 #include numpy相关vs2019配置 import_array();报错 参考&#xff1a;https://blog.csdn.net/weixin_40232401/article/details/106944336#:~:text%E5%9C%A8,import_array%20%28%29%E5%87%BA%E7%8E%B0%E6%8A%A5%E9%94%99%EF%BC%8C%E6…

renix如何查看时延和抖动和丢包——网络测试仪实操

目录 查看时延和抖动​ 一、预约测试资源 ​ 二、新建流​ 三、查看时延和抖动​ 查看丢包​ 一、预约端口​ 二、创建Raw流​ 三、如何查看流量的实时丢包个数和丢包比例​ 查看时延和抖动​ 一、预约测试资源 ​ 打开Renix软件&#xff0c;连接机箱, 预约端口​ 二…

课题-基于安卓androidstudio的团购app

一、课题介绍 客户端&#xff1a; 1&#xff1a;注册登录&#xff1a;用户使用注册的账号密码进行登录&#xff1b; 2&#xff1a;查看商品&#xff1a;用户可以查看发布的商品信息&#xff1b; 3&#xff1a;分类查看&#xff1a;用户可以通过分类的查看商品信息&#xff1b;…

MySQL索引的数据结构

索引的数据结构 本专栏学习内容来自尚硅谷宋红康老师的视频 有兴趣的小伙伴可以点击视频地址观看 1. 为什么要使用索引&#xff1f; 索引是存储引擎用于快速找到数据记录的一种数据结构&#xff0c;就好比去图书馆找书&#xff0c;或者新华字典里找字&#xff0c;相当于一个目…

SQL用法详解

1.SQL语言是什么?有什么作用?SQL:结构化查询语言&#xff0c;用于操作数据库&#xff0c;通用于绝大多数的数据库软件2.SQL的特征大小写不敏感需以;号结尾支持单行、多行注释3操作数据库的SQL语言基于功能可以划分为4类:数据定义:DDL ( Data Definition Language)&#xff1a…

校验、异常处理

前端校验完后&#xff0c;后端需要再做一次校验 JSR303 定义了数据校验的标准 使用步骤 为Bean标识注解&#xff0c;并自定义错误提示 import javax.validation.constraints自定义规则&#xff1a;一个小写或大写字母 Email、Future、NotBlank、Size 等 不推荐使用NotEmp…

Linux下命令(2)

Linux下命令(2) 1. 解压缩命令 Linux 下最常用的打包程序是 tar 命令&#xff0c; 使用 tar 打出来的包我们常称为 tar 包&#xff0c; tar包文件的命令通常都是以.tar 结尾的&#xff0c;生成 tar 包后&#xff0c;就可以用其它的程序来进行压缩了。   功能&#xff1a; ta…

Python程序的构成

1.开始学习图形化程序设计 >>> import turtle #导入turtle模块 >>> turtle.showturtle() #显示箭头 >>> turtle.write("文字") #写字符串 >>> turtle.forward(300) #前进300像素 >>> turtle.c…

基于C++的AGV机器人无线控制实现

AGV系统概述 AGV原理 AGV行走控制系统由控制面板、导向传感器、方向电位器、状态指示灯、避障传感器、光电控制信号传感器、驱动单元、导引磁条、电源组成。 AGV的导引&#xff08;Guidance&#xff09;是指根据AGV导向传感器&#xff08;Navigation&#xff09;所得到的位置…

Blender如何打开IFC数据?

Blender如何打开IFC数据安装blenderbimIFC介绍下载和安装BlenderBIM插件Blender打开IFC数据对于一个外行人&#xff0c;当我想查看IFC数据的呈现形式时&#xff0c;但是我又没有Revit软件&#xff0c;那么我想到了Blender&#xff0c;网上查了只需要安装BlenderBIM插件&#xf…

表单验证[用户名、邮箱、密码、重复密码]

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>表单验证</title> <link rel"stylesheet" href"form.css"> <!-- 引入样式 --> &l…

C++语法3——if switch break continue的定义及用法

接上节 循环语句 这一节写的是判断语句 if else语句 基本语法&#xff1a; 第一种&#xff1a; if(bool(布尔变量)) {如果bool值为真执行的语句; } else {如果bool值为假执行的语句; }如果布尔表达式为 true&#xff0c;则执行 if 块内的代码。如果布尔表达式为 false&#x…

2023北京/上海/广州/深圳物联网产品经理班招生简章

NPDP产品经理国际资格认证是国际公认的唯一的新产品开发专业认证&#xff0c;集理论、方法与实践为一体的全方位的知识体系&#xff0c;为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 我们针对互联网时代的个人、互联网企业、与传统企业推出一系列学习。 课程从…

第四十五讲:神州防火墙P2P流量控制配置

实验拓扑图如下所示 配置要求&#xff1a;出口带宽 100Mbps&#xff0c;外网为 eth0/1 接口&#xff0c; 内网连接两个网段172.16.1.0/24 和 192.168.1.0/24&#xff0c;需限制 P2P 应用其下行带宽为 10M&#xff0c;上传最大 5M。 配置步骤&#xff1a; 一、指定接口带宽 …

Struts2框架之result配置

Struts2框架之result配置result配置1、常用的结果类型1.1、dispather类型1.2、redirect类型1.3、redirectAction类型2、全局结果配置result配置 result配置一般与页面相关&#xff0c;请求经由对应Action处理后&#xff0c;返回一个字符串&#xff0c;根据返回的字符串找到对应…

为了让5G更省电,这家设备商秀出黑科技

近日&#xff0c;工信部发布了我国最新的《通信业经济运行情况》数据。根据数据显示&#xff0c;截止到11月末&#xff0c;我国5G基站总数已经达到228.7万个&#xff0c;比2021年底增加了86.2万个。这些数量庞大的基站&#xff0c;共同构建了一张规模庞大的5G网络。而这张网络&…

YOLOv5 CPU实时的实例分割教程-它来了!

一个不知名大学生&#xff0c;江湖人称菜狗original author: jacky LiEmail : 3435673055qq.comTime of completion&#xff1a;2023.1.6Last edited: 2023.1.6YOLOv5 CPU实时的实例分割教程-它来了&#xff01;简介前不久&#xff0c;ultralytics发布了一个yolov5 7.0版本&…

yolov8s网络模型结构图

yolov8&#xff01;&#xff01;&#xff01;&#xff01; yolov8&#xff01;&#xff01;&#xff01;&#xff01; yolov8&#xff01;&#xff01;&#xff01;&#xff01; yolov8&#xff01;&#xff01;&#xff01;&#xff01; yolov8真的来了&#xff01;&#…

2023,本命年向阳而生

2023&#xff0c;本命年向阳而生 ——Maynor的2022复盘及2023目标 幸运且努力 先说结论&#xff1a;2022年是极不平凡的一年。 有很多重大事件发生&#xff0c;且与我们的生活息息相关。最令人高兴的是疫情的缓解&#xff0c;2023年也将有更多的机会。 我在这一年经历的事…