useEffect与useLayoutEffect

useEffectuseLayoutEffect可以统称为Effect HookEffect Hook可以在函数组件中执行副作用操作,副作用是指函数或者表达式的行为依赖于外部环境,或者在这里可以理解为修改了某状态会对其他的状态造成影响,这个影响就是副作用,数据获取,设置订阅以及手动更改React组件中的DOM都属于副作用。

useEffect

useEffect Hook可以看做 componentDidMountcomponentDidUpdatecomponentWillUnmount这三个生命周期函数的组合,但是使用多个Effect实现关注点分离,也就是说useEffect的粒度更低,可以将各个关注的位置分离处理副作用。
既然是对componentDidMountcomponentDidUpdatecomponentWillUnmount这三个生命周期函数的组合,那么我们也可以使用useEffect将其分离,首先对于componentDidMountcomponentWillUnmount,也就是想执行只运行一次的 effect(仅在组件挂载和卸载时执行),由于不存在任何依赖,那么对于第二个参数就是一个空的数组。如果省略了第二个参数的话,那么在组件的初始化和更新都会执行,一般情况下是并不希望这样的,因为Hooks的设计,每次setState都会重新执行组件函数,这样的话副作用函数就会频繁执行,所以通常来说还是尽量不要省略第二个参数。回到生命周期,通常如果在组件建立时建立了一个定时器,那么我们希望在组件销毁的时候将定时器销毁来避免内存泄露,那么在useEffect中返回一个函数调用去关闭定时器即可,在这里我们的关注点可以集中在一起而不用再分开两个生命周期去写了。

import { useEffect, useState } from "react"; import "./styles.css"; export default function App() { const [count, setCount] = useState(0); useEffect(() => { console.log("Component is mounted"); return () => console.log("Component is unmounted"); }, []); useEffect(() => { console.log("Component is mounted or updated"); }) return ( <div> <div>{count}</div> <button onClick={() => setCount(count + 1)}>count + 1</button> </div> ); }

对于componentDidUpdate,之前如果是写class组件实现相同的功能的话,就需要在这个生命周期中嵌入很多的逻辑,使用useEffect就可以将各个关注点分离,分别处理其副作用,当然如果依然需要解除诸如订阅或者定时器等,依旧可以返回一个处理函数来处理。

import { useEffect, useState } from "react"; import "./styles.css"; export default function App() { const [count, setCount] = useState(0); useEffect(() => { console.log("Count is updated"); document.title = `count: ${count}`; }, [count]); return ( <div> <div>{count}</div> <button onClick={() => setCount(count + 1)}>count + 1</button> </div> ); }

在文档中还指出请确保数组中包含了所有外部作用域中会随时间变化并且在effect中使用的变量,否则你的代码会引用到先前渲染中的旧变量。如果你传入了一个空数组[]effect内部的propsstate就会一直拥有其初始值。下面这个例子就会出现一个bug,在依赖数组中没有传递count,那么就会导致当effect执行时,创建的effect闭包会将count的值保存在该闭包当中,且初值为0,每隔一秒回调就会执行setCount(0 + 1),因此count永远不会超过1,此时如果我们将count加入到依赖数组中便可解决这个问题。对于这个问题,React提供了一个exhaustive-depsESLint规则作为eslint-plugin-react-hooks包的一部分,它会帮助你找出无法一致地处理更新的组件。

import { useEffect, useState } from "react"; import "./styles.css"; export default function App() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { setCount(count + 1); console.log(count + 1); }, 1000); return () => clearInterval(id); }, []); // `count` 没有被指定为依赖 return ( <div> <div>{count}</div> <button onClick={() => setCount(count + 1)}>count + 1</button> </div> ); }

看起来和VueWatch很像,但是又不尽然相同,语法上的区别主要就在于useEffect可以监控多个属性的变化,Watch不行,当然Watch可以通过间接的方式实现,但是思想方面是不同的,Vue是监听值的变化而React是用以处理副作用。提到这个的主要原因是因为之前写Vue较多,就老想着通过Vue的角度来类比React的各项实现,感觉这样有好处也有弊端,好处就是很快能够上手,坏处就是很容易钻牛角尖,或者很容易陷入一个围城。有位大佬说的挺好的,你需要把Vue忘掉再来学习Hooks,虽然并不绝对但也很有道理。
当函数组件刷新渲染时,包含useEffect的组件整个运行过程如下:

  • 触发组件重新渲染,通过改变组件state或者组件的父组件重新渲染,导致子节点渲染。
  • 组件函数执行。
  • 组件渲染后呈现到屏幕上。
  • useEffect hook执行。

useLayoutEffect

useLayoutEffectuseEffect很像,函数签名也是一样,唯一的不同点就是useEffect是异步执行,而useLayoutEffect是同步执行的。当函数组件刷新渲染时,包含useLayoutEffect的组件整个运行过程如下:

  • 触发组件重新渲染,通过改变组件state或者组件的父组件重新渲染,导致子组件渲染。

  • 组件函数执行。

  • useLayoutEffect hook执行,React等待useLayoutEffect的函数执行完毕。

  • 组件渲染后呈现到屏幕上。

每日一题

https://github.com/WindrunnerMax/EveryDay

参考

https://zhuanlan.zhihu.com/p/348701319 https://zhuanlan.zhihu.com/p/259766064 https://segmentfault.com/a/1190000039087645 http://www.ptbird.cn/react-hoot-useEffect.html https://react.docschina.org/docs/hooks-effect.html https://pengfeixc.com/blog/605af93600f1525af762a725 https://react.docschina.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies