From 9de138917cc4e26fb05592edf27dc77269427520 Mon Sep 17 00:00:00 2001
From: xie jie <745007854@qq.com>
Date: Wed, 25 Dec 2024 18:39:46 +0800
Subject: [PATCH] update hook answer
---
.../2-21. effect相关hook/effect相关hook.md | 720 +++++++++---------
1 file changed, 366 insertions(+), 354 deletions(-)
diff --git a/就业篇/02. 第二章/2-21. effect相关hook/effect相关hook.md b/就业篇/02. 第二章/2-21. effect相关hook/effect相关hook.md
index b35623c..f6e4dc2 100644
--- a/就业篇/02. 第二章/2-21. effect相关hook/effect相关hook.md
+++ b/就业篇/02. 第二章/2-21. effect相关hook/effect相关hook.md
@@ -1,354 +1,366 @@
-# effect相关hook
-
-> 面试题:说一说 useEffect 和 useLayoutEffect 的区别?
-
-
-
-在 React 中,用于定义有副作用的因变量的 hook 有三个:
-
-- useEffect:回调函数会在 commit 阶段完成后异步执行,所以它不会阻塞视图渲染
-- useLayoutEffect:回调函数会在 commit 阶段的 Layout 子阶段同步执行,一般用于执行 DOM 相关的操作
-- useInsertionEffect:回调函数会在 commit 阶段的 Mutation 子阶段同步执行,与 useLayoutEffect 的区别在于执行的时候无法访问对 DOM 的引用。这个 Hook 是专门为 CSS-in-JS 库插入全局的 style 元素而设计。
-
-
-
-## 数据结构
-
-对于这三个 effect 相关的 hook,hook.memoizedState 共同使用同一套数据结构:
-
-```js
-const effect = {
- // 用于区分 effect 类型 Passive | Layout | Insertion
- tag,
- // effect 回调函数
- create,
- // effect 销毁函数
- destory,
- // 依赖项
- deps,
- // 与当前 FC 的其他 effect 形成环状链表
- next: null
-}
-```
-
-tag 用来区分 effect 的类型:
-
-- Passive: useEffect
-- Layout:useLayoutEffect
-- Insertion:useInsertionEffect
-
-
-
-create 和 destory 分别指代 effect 的回调函数以及 effect 销毁函数:
-
-```js
-useEffect(()=>{
- // create
- return ()=>{
- // destory
- }
-})
-```
-
-
-
-next 字段会与当前的函数组件的其他 effect 形成环状链表,连接的方式是一个单向环状链表。
-
-```jsx
-function App(){
- useEffect(()=>{
- console.log(1);
- });
- const [num1, setNum1] = useState(0);
- const [num2, setNum2] = useState(0);
- useEffect(()=>{
- console.log(2);
- });
- useEffect(()=>{
- console.log(3);
- });
-
- return
Hello
-}
-```
-
-结构如下图所示:
-
-
-
-
-
-## 工作流程
-
-整个工作流程可以分为三个阶段:
-
-- 声明阶段
-- 调度阶段(useEffect 独有的)
-- 执行阶段
-
-
-
-### 声明阶段
-
-声明阶段又可以分为 mount 和 update。
-
-mount 的时候执行的是 mountEffectImpl,相关代码如下:
-
-```js
-function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
- // 生成 hook 对象
- const hook = mountWorkInProgressHook();
- // 保存依赖的数组
- const nextDeps = deps === undefined ? null : deps;
- // 修改当前 fiber 的 flag
- currentlyRenderingFiber.flags |= fiberFlags;
- // 将 pushEffect 返回的环形链表存储到 hook 对象的 memoizedState 中
- hook.memoizedState = pushEffect(
- HookHasEffect | hookFlags,
- create,
- undefined,
- nextDeps
- );
-}
-```
-
-在上面的代码中,首先生成 hook 对象,拿到依赖,修改 fiber 的 flag,之后将当前的 effect 推入到环状列表,hook.memoizedState 指向该环状列表。
-
-
-
-update 的时候执行的是 updateEffectImpl,相关代码如下:
-
-```js
-function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
- // 先拿到之前的 hook 对象
- const hook = updateWorkInProgressHook();
- // 拿到依赖项
- const nextDeps = deps === undefined ? null : deps;
-
- // 初始化清除 effect 函数
- let destroy = undefined;
-
- if (currentHook !== null) {
- // 从 hook 对象上面的 memoizedState 上面拿到副作用的环形链表
- const prevEffect = currentHook.memoizedState;
- // 拿到销毁函数,也就是说副作用函数执行后返回的函数
- destroy = prevEffect.destroy;
- // 如果新的依赖项不为空
- if (nextDeps !== null) {
- const prevDeps = prevEffect.deps;
- // 两个依赖项进行比较
- if (areHookInputsEqual(nextDeps, prevDeps)) {
- // 如果依赖的值相同,即依赖没有变化,那么只会给这个 effect 打上一个 HookPassive 一个 tag
- // 然后在组件渲染完以后会跳过这个 effect 的执行
- hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
- return;
- }
- }
- }
- // 如果deps依赖项发生改变,赋予 effectTag ,在commit节点,就会再次执行我们的effect
- currentlyRenderingFiber.flags |= fiberFlags;
-
- // pushEffect 的作用是将当前 effect 添加到 FiberNode 的 updateQueue 中,然后返回这个当前 effcet
- // 然后是把返回的当前 effect 保存到 Hook 节点的 memoizedState 属性中
- hook.memoizedState = pushEffect(
- HookHasEffect | hookFlags,
- create,
- destroy,
- nextDeps
- );
-}
-```
-
-在上面的代码中,首先从 updateWorkInProgressHook 方法中拿到 hook 对象,之后会从 hook.memoizedState 拿到所存储的 effect 对象,之后会利用 areHookInputsEqual 方法进行前后依赖项的比较,如果依赖相同,那就会在 effect 上面打一个 tag,在组件渲染完以后会跳过这个 effect 的执行。
-
-如果依赖发生了变化,那么当前的 fiberNode 就会有一个 flags,回头在 commit 阶段统一执行该 effect,之后会推入新的 effect 到环状链表上面。
-
-
-
-areHookInputsEqual 的作用是比较两个依赖项数组是否相同,采用的是浅比较,相关代码如下:
-
-```js
-function areHookInputsEqual(nextDeps, prevDeps){
- // 省略代码
- for(let i=0; i {
- // 执行 effect 回调函数的具体方法
- flushPassiveEffects();
- return null;
- });
- }
-}
-```
-
-flushPassiveEffects 会去执行对应的 effects:
-
-```js
-function flushPassiveEffects(){
- if (rootWithPendingPassiveEffects !== null) {
- // 执行 effects
- }
- return false;
-}
-```
-
-另外,由于调度阶段的存在,为了保证下一次的 commit 阶段执行前,上一次 commit 所调度的 useEffect 都已经执行过了,因此会在 commit 阶段的入口处,也会执行 flushPassiveEffects,而且是一个循环执行:
-
-```js
-function commitRootImpl(root, renderPriorityLevel){
- do {
- flushPassiveEffects();
- } while (rootWithPendingPassiveEffects !== null);
-}
-```
-
-之所以使用 do...while 循环,就是为了保证上一轮调度的 effect 都执行过了。
-
-
-
-### 执行阶段
-
-这三个 effect 相关的 hook 执行阶段,有两个相关的方法
-
-- commitHookEffectListUnmount :用于遍历 effect 链表依次执行 effect.destory 方法
-
-```js
-function commitHookEffectListUnmount(
- flags: HookFlags,
- finishedWork: Fiber,
- nearestMountedAncestor: Fiber | null,
-) {
- const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
- const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
- if (lastEffect !== null) {
- const firstEffect = lastEffect.next;
- let effect = firstEffect;
- do {
- if ((effect.tag & flags) === flags) {
- // Unmount
- // 从 effect 对象上面拿到 destory 函数
- const destroy = effect.destroy;
- effect.destroy = undefined;
- // ...
- }
- effect = effect.next;
- } while (effect !== firstEffect);
- }
-}
-```
-
-- commitHookEffectListMount:遍历 effect 链表依次执行 create 方法,在声明阶段中,update 时会根据 deps 是否变化打上不同的 tag,之后在执行阶段就会根据是否有 tag 来决定是否要执行该 effect
-
-```js
-// 类型为 useInsertionEffect 并且存在 HasEffect tag 的 effect 会执行回调
-commitHookEffectListMount(Insertion | HasEffect, fiber);
-// 类型为 useEffect 并且存在 HasEffect tag 的 effect 会执行回调
-commitHookEffectListMount(Passive | HasEffect, fiber);
-// 类型为 useLayoutEffect 并且存在 HasEffect tag 的 effect 会执行回调
-commitHookEffectListMount(Layout | HasEffect, fiber);
-```
-
-
-
-由于 commitHookEffectListUnmount 方法的执行时机会先于 commitHookEffectListMount 方法执行,因此每次都是先执行 effect.destory 后才会执行 effect.create。
-
-
-
-## 真题解答
-
-> 题目:说一说 useEffect 和 useLayoutEffect 的区别?
->
-> 参考答案:
->
->
+# effect相关hook
+
+> 面试题:说一说 useEffect 和 useLayoutEffect 的区别?
+
+
+
+在 React 中,用于定义有副作用的因变量的 hook 有三个:
+
+- useEffect:回调函数会在 commit 阶段完成后异步执行,所以它不会阻塞视图渲染
+- useLayoutEffect:回调函数会在 commit 阶段的 Layout 子阶段同步执行,一般用于执行 DOM 相关的操作
+- useInsertionEffect:回调函数会在 commit 阶段的 Mutation 子阶段同步执行,与 useLayoutEffect 的区别在于执行的时候无法访问对 DOM 的引用。这个 Hook 是专门为 CSS-in-JS 库插入全局的 style 元素而设计。
+
+## 数据结构
+
+对于这三个 effect 相关的 hook,hook.memoizedState 共同使用同一套数据结构:
+
+```js
+const effect = {
+ // 用于区分 effect 类型 Passive | Layout | Insertion
+ tag,
+ // effect 回调函数
+ create,
+ // effect 销毁函数
+ destory,
+ // 依赖项
+ deps,
+ // 与当前 FC 的其他 effect 形成环状链表
+ next: null
+}
+```
+
+tag 用来区分 effect 的类型:
+
+- Passive: useEffect
+- Layout:useLayoutEffect
+- Insertion:useInsertionEffect
+
+
+
+create 和 destory 分别指代 effect 的回调函数以及 effect 销毁函数:
+
+```js
+useEffect(()=>{
+ // create
+ return ()=>{
+ // destory
+ }
+})
+```
+
+
+
+next 字段会与当前的函数组件的其他 effect 形成环状链表,连接的方式是一个单向环状链表。
+
+```jsx
+function App(){
+ useEffect(()=>{
+ console.log(1);
+ });
+ const [num1, setNum1] = useState(0);
+ const [num2, setNum2] = useState(0);
+ useEffect(()=>{
+ console.log(2);
+ });
+ useEffect(()=>{
+ console.log(3);
+ });
+
+ return Hello
+}
+```
+
+结构如下图所示:
+
+
+
+## 工作流程
+
+整个工作流程可以分为三个阶段:
+
+- 声明阶段
+- 调度阶段(useEffect 独有的)
+- 执行阶段
+
+
+
+### 声明阶段
+
+声明阶段又可以分为 mount 和 update。
+
+mount 的时候执行的是 mountEffectImpl,相关代码如下:
+
+```js
+function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
+ // 生成 hook 对象
+ const hook = mountWorkInProgressHook();
+ // 保存依赖的数组
+ const nextDeps = deps === undefined ? null : deps;
+ // 修改当前 fiber 的 flag
+ currentlyRenderingFiber.flags |= fiberFlags;
+ // 将 pushEffect 返回的环形链表存储到 hook 对象的 memoizedState 中
+ hook.memoizedState = pushEffect(
+ HookHasEffect | hookFlags,
+ create,
+ undefined,
+ nextDeps
+ );
+}
+```
+
+在上面的代码中,首先生成 hook 对象,拿到依赖,修改 fiber 的 flag,之后将当前的 effect 推入到环状列表,hook.memoizedState 指向该环状列表。
+
+
+
+update 的时候执行的是 updateEffectImpl,相关代码如下:
+
+```js
+function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
+ // 先拿到之前的 hook 对象
+ const hook = updateWorkInProgressHook();
+ // 拿到依赖项
+ const nextDeps = deps === undefined ? null : deps;
+
+ // 初始化清除 effect 函数
+ let destroy = undefined;
+
+ if (currentHook !== null) {
+ // 从 hook 对象上面的 memoizedState 上面拿到副作用的环形链表
+ const prevEffect = currentHook.memoizedState;
+ // 拿到销毁函数,也就是说副作用函数执行后返回的函数
+ destroy = prevEffect.destroy;
+ // 如果新的依赖项不为空
+ if (nextDeps !== null) {
+ const prevDeps = prevEffect.deps;
+ // 两个依赖项进行比较
+ if (areHookInputsEqual(nextDeps, prevDeps)) {
+ // 如果依赖的值相同,即依赖没有变化,那么只会给这个 effect 打上一个 HookPassive 一个 tag
+ // 然后在组件渲染完以后会跳过这个 effect 的执行
+ hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
+ return;
+ }
+ }
+ }
+ // 如果deps依赖项发生改变,赋予 effectTag ,在commit节点,就会再次执行我们的effect
+ currentlyRenderingFiber.flags |= fiberFlags;
+
+ // pushEffect 的作用是将当前 effect 添加到 FiberNode 的 updateQueue 中,然后返回这个当前 effcet
+ // 然后是把返回的当前 effect 保存到 Hook 节点的 memoizedState 属性中
+ hook.memoizedState = pushEffect(
+ HookHasEffect | hookFlags,
+ create,
+ destroy,
+ nextDeps
+ );
+}
+```
+
+在上面的代码中,首先从 updateWorkInProgressHook 方法中拿到 hook 对象,之后会从 hook.memoizedState 拿到所存储的 effect 对象,之后会利用 areHookInputsEqual 方法进行前后依赖项的比较,如果依赖相同,那就会在 effect 上面打一个 tag,在组件渲染完以后会跳过这个 effect 的执行。
+
+如果依赖发生了变化,那么当前的 fiberNode 就会有一个 flags,回头在 commit 阶段统一执行该 effect,之后会推入新的 effect 到环状链表上面。
+
+
+
+areHookInputsEqual 的作用是比较两个依赖项数组是否相同,采用的是浅比较,相关代码如下:
+
+```js
+function areHookInputsEqual(nextDeps, prevDeps){
+ // 省略代码
+ for(let i=0; i {
+ // 执行 effect 回调函数的具体方法
+ flushPassiveEffects();
+ return null;
+ });
+ }
+}
+```
+
+flushPassiveEffects 会去执行对应的 effects:
+
+```js
+function flushPassiveEffects(){
+ if (rootWithPendingPassiveEffects !== null) {
+ // 执行 effects
+ }
+ return false;
+}
+```
+
+另外,由于调度阶段的存在,为了保证下一次的 commit 阶段执行前,上一次 commit 所调度的 useEffect 都已经执行过了,因此会在 commit 阶段的入口处,也会执行 flushPassiveEffects,而且是一个循环执行:
+
+```js
+function commitRootImpl(root, renderPriorityLevel){
+ do {
+ flushPassiveEffects();
+ } while (rootWithPendingPassiveEffects !== null);
+}
+```
+
+之所以使用 do...while 循环,就是为了保证上一轮调度的 effect 都执行过了。
+
+
+
+### 执行阶段
+
+这三个 effect 相关的 hook 执行阶段,有两个相关的方法
+
+- commitHookEffectListUnmount :用于遍历 effect 链表依次执行 effect.destory 方法
+
+```js
+function commitHookEffectListUnmount(
+ flags: HookFlags,
+ finishedWork: Fiber,
+ nearestMountedAncestor: Fiber | null,
+) {
+ const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
+ const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
+ if (lastEffect !== null) {
+ const firstEffect = lastEffect.next;
+ let effect = firstEffect;
+ do {
+ if ((effect.tag & flags) === flags) {
+ // Unmount
+ // 从 effect 对象上面拿到 destory 函数
+ const destroy = effect.destroy;
+ effect.destroy = undefined;
+ // ...
+ }
+ effect = effect.next;
+ } while (effect !== firstEffect);
+ }
+}
+```
+
+- commitHookEffectListMount:遍历 effect 链表依次执行 create 方法,在声明阶段中,update 时会根据 deps 是否变化打上不同的 tag,之后在执行阶段就会根据是否有 tag 来决定是否要执行该 effect
+
+```js
+// 类型为 useInsertionEffect 并且存在 HasEffect tag 的 effect 会执行回调
+commitHookEffectListMount(Insertion | HasEffect, fiber);
+// 类型为 useEffect 并且存在 HasEffect tag 的 effect 会执行回调
+commitHookEffectListMount(Passive | HasEffect, fiber);
+// 类型为 useLayoutEffect 并且存在 HasEffect tag 的 effect 会执行回调
+commitHookEffectListMount(Layout | HasEffect, fiber);
+```
+
+
+
+由于 commitHookEffectListUnmount 方法的执行时机会先于 commitHookEffectListMount 方法执行,因此每次都是先执行 effect.destory 后才会执行 effect.create。
+
+## 真题解答
+
+> 题目:说一说 useEffect 和 useLayoutEffect 的区别?
+>
+> 参考答案:
+>
+> 在 React 中,用于定义有副作用因变量的 Hook 有:
+>
+> - useEffect:回调函数会在 commit 阶段完成后异步执行,所以不会阻塞视图渲染
+> - useLayoutEffect:回调函数会在 commit 阶段的 Layout 子阶段同步执行,一般用于执行 DOM 相关的操作
+>
+> 每一个 effect 会与当前 FC 其他的 effect 形成环状链表,连接方式为单向环状链表。
+>
+> 其中 useEffect 工作流程可以分为:
+>
+> - 声明阶段
+> - 调度阶段
+> - 执行阶段
+>
+> useLayoutEffect 的工作流程可以分为:
+>
+> - 声明阶段
+> - 执行阶段
+>
+> 之所以 useEffect 会比 useLayoutEffect 多一个阶段,就是因为 useEffect 的回调函数会在 commit 阶段完成后异步执行,因此需要经历调度阶段。