获取中...

-

Just a minute...

为什么一定要useEffect?

之前看到过,说不要把副作用直接写在函数式组件的函数体内,需要用useEffect把副作用包裹起来。我当时的理解就是,函数组件重新渲染的话,整个函数都会被执行一遍。如果我就想让一个副作用,每次组件渲染都重新执行,为什么一定要放在useEffect里面?

太长不看:为了不阻塞界面。(不过好像react18的新特性,useEffect也会阻塞了)

useEffect在界面绘制完毕后执行

后来我偶然看到一句话,意思是说useEffect里面的副作用,是在渲染完毕之后才执行的。那问题又来了啊,我如果要在useEffect里面去改state咋办,那你渲染完了我再改state,那不是又要重新渲染?为啥不在渲染之前执行呢?

这就涉及到react渲染机制的问题了。我们可以简单地把react的渲染分成两个阶段:render和commit。其中render就是创造vnode(也就是执行类组件的render方法,或者执行函数组件的函数体,去得到vnode),并且比较新旧vnode(其实是vnode转化的fiber tree)的过程。而commit就是将改动提交,将vnode转成真实dom,并且将真实dom绘制出来的过程,涉及到了浏览器的重排。

而为了不阻塞界面的渲染(用户感觉界面卡顿),useEffect就被放到了界面渲染完毕后执行,至少在react16中是这样的。不过这样就会导致前面提到的问题,在useEffect中修改state,会导致连续两次的渲染(浏览器两次重新绘制),可能出现闪屏的情况,因为第一次渲染的结果可能刚出现,就要被第二次渲染的结果替代掉。

如果有这种情况,或者需要在useEffect中获取最新dom信息,可以使用useLayoutEffect这个hook,这个hook和useEffect的唯一区别就是,useLayoutEffect是同步执行的。它的执行是在,得到真实dom之后 绘制到浏览器上之前。等到useLayoutEffect执行完毕之后,浏览器才开始绘制。并且如果执行useLayoutEffect的时候更改了state,浏览器会中止第一次绘制,直接去等第二次绘制的结果。我们用下面的代码,结合视频中界面的变化和控制台的输出,来更好地理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const App = () => {
const [value, setValue] = useState(0);

const start = performance.now();
console.log("坐牢开始", start);
while (performance.now() - start < 2000) {}
console.log("坐牢结束", performance.now());
useLayoutEffect(()=>{
console.log("执行useLayoutEffect",performance.now())
const start = performance.now();
while (performance.now() - start < 2000) {}
console.log("useLayoutEffect执行完毕",performance.now())
})

useEffect(() => {
console.log("执行useEffect", performance.now());
if (value === 0) {
const start = performance.now();
while (performance.now() - start < 2000) {}
setValue(10 + Math.random() * 200);
}
console.log("useEffect执行完毕")
}, [value]);

return (
<>
<>{console.log('执行render', performance.now())}</>
<div onClick={() => setValue(0)}>value: {value}</div>
</>
);
};

代码的逻辑是,每次点击,都先将value设为0,再将value设为一个随机值。value的两次变化会导致界面被渲染两次。我们通过插入多个2秒的无条件循环,来拉长整个过程。我们可以发现,当useLayoutEffect执行完毕的时候,界面立刻发生了变化,之后才执行useEffect。也就是说,useEffect确实是发生在界面完全渲染之后的。

React18的新特性

然而事情并没有这么简单。React18的新特性,疑似将useEffect的执行时机也提到了渲染之前。如下面视频所示,同样的一段代码在React18中有着完全不同的表现。

我们发现,在useEffect执行完毕之前,界面都没有任何变化。说明useEffect也阻塞了界面的渲染。不过和useLayoutEffect不同的是,useEffect虽然阻塞了渲染,却并不会因为更改了state,让浏览器中止第一次绘制,我们还是能看到中间value为0的状态。

总结

React16中,渲染流程的执行顺序是,

函数组件函数体->useLayoutEffect(可阻塞并打断绘制)>浏览器绘制->useEffect

React18中,渲染流程的执行顺序是,

函数组件函数体->useLayoutEffect(可阻塞并打断绘制)->useEffect(可阻塞,但不能打断绘制)->浏览器绘制

以上为React初学者对React的自行探究,如果错误还望指出

相关文章
评论
分享
  • 动手实现react-ssr 2

    仓库链接:本篇代码 运行方法参考readme,请使用pnpm,因为本次使用的rspack尚未推出1.0版本,预计后续可能会有很多breakchange,所以lock文件至关重要,而我只上传了pnpm的lock,非常抱歉。 环境:no...

    动手实现react-ssr 2
  • 动手实现react ssr

    关于ssr相关的介绍,可以移步之前的一篇博文,本文不再赘述 日常工作中,我们可能都接触过ssr,不过我们可能通常是借助于框架(例如Next.js)的支持来完成ssr。今天我想尝试,摆脱高度封装的前端框架,只依赖react相关的库,来实...

    动手实现react ssr
  • 从invalid hook call报错说到npm install相关

    遇到了invalid hook call报错上周,业务下游的同事反馈了一个生产问题,说我们传的值有问题,领导让我帮忙排查一下。我最近一直没做那个项目相关的需求,打开那个项目切到release分支,拉代码,npm i然后启动,结果就白屏...

    从invalid hook call报错说到npm install相关
  • 聊聊react与函数式编程

    react不是魔法 今天我想来聊一聊函数式编程,和它在react中的体现。 何为函数式编程我们可以把函数式编程理解成一种范式,一个规范。纯函数是函数式编程的关键概念,函数式编程希望我们尽可能多地使用纯函数。正是依赖于纯函数的特点,...

    聊聊react与函数式编程
  • react的一些坑

    记录一些刚写react踩过的坑吧,想到多少就写多少 从一个工作中的需求开始有个需求是,点击按钮出来一个弹窗。本来弹窗有个关闭按钮,但是UED在视觉稿上面加了行字,“点击关闭按钮或弹窗外的其他地方,关闭弹窗”。我想了想,合理,避免有...

    react的一些坑
  • 使用react的一些心得与感想

    刚忙完公司的新人训练营,9个人的团队2周完成一个小项目。我负责用remax开发一个微信小程序。remax就是蚂蚁的一个开源框架,让开发者能用react的语法去开发微信小程序。开发体验整体来讲非常不错,感觉也加深了我对react的理解...

    使用react的一些心得与感想
  • 基于tapable来模拟webpack插件机制

    前两天看到ByteFE公众号发了篇文章,从源码去讲webpack。我之前也从很多方面去学习webpack,只是源码一直没太看得进去,这一次我想仔细地读一读。 简要介绍tapable说实话,之前看webpack源码一直没看的太懂,...

    基于tapable来模拟webpack插件机制
  • 深入js——作用域,作用域链,执行栈(一)

    最近在补js基础。当初自学js的时候,总是感觉学的不扎实。只能说不去上手,再怎么死读书,也理解不了。积累了经验,然后自己去思考,这个时候再去看理论,就容易理解了。 感觉工作一年以来,确实学到了很多,但是过于专注于应用方面的知识,反倒...

    深入js——作用域,作用域链,执行栈(一)
  • webpack运行时代码简要分析

    开个坑,主要也是怕自己后面忘了 简单写写,有空了补上 __webpack_modules是一个数组,每个元素都是一个函数,函数接受三个参数:第一个参数module好像没用到,第二个是一个对象(会把本模块要导出的属性,赋值给这个对象),...

    webpack运行时代码简要分析
  • webpack动态加载原理简述

    为什么需要动态加载上一篇讲我写webpack针对markdown的loader的时候,提到了希望能动态加载以优化性能。篇幅原因没有展开说,打算单独写一篇文章讲一下。 当我们访问网站的时候,浏览器会向服务器请求页面资源。首先请求的当然是...

    webpack动态加载原理简述
Please check the parameter of comment in config.yml of hexo-theme-Annie!