【react】为什么说只能在最顶层使用 Hook

为什么说只能在最顶层使用 Hook

相关文章

为什么说只能在最顶层使用 Hook


一、为什么说只能在最顶层使用 Hook

这是因为React依赖于Hook的调用顺序来正确地管理组件的状态。

当你在React函数组件中使用Hook时,React会依赖于Hook的调用顺序来确定哪个状态对应于哪个调用。如果Hook在组件的不同渲染间调用顺序发生了变化,React可能会产生意外的结果,例如状态错乱或数据丢失。

在函数组件中,Hook的调用必须保持一致和稳定,这意味着你不能在条件语句、循环或嵌套函数内部调用Hook,因为这样会导致Hook的调用顺序可能随着组件的重新渲染而发生变化。

二、怎么理解React会依赖于Hook的调用顺序来确定哪个状态对应于哪个调用

示例

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
import { useState } from "react";

export default function MyComponent() {
const [number, setNumber] = useState(0);
const [showMore, setShowMore] = useState(false);

function handleNextNumber() {
setNumber(number + 1);
}

function handleMoreClick() {
setShowMore(!showMore);
}

return (
<>
<button onClick={handleNextNumber}>Next</button>
<h3>{number + 1}</h3>
<button onClick={handleMoreClick}>
{showMore ? "Hide" : "Show"} details
</button>
{showMore && <p>Hello World!</p>}
</>
);
}

上方代码中,使用了两个useState 的 Hook,我们并不会将可识别的值传入useState,那React 是如何知道哪个 state 会对应到哪个useState 呢?答案是,如果每一次 Hook 的调用顺序是稳定的,React 就能够知道哪个state 对应到哪个useState。

三、Hook 背后工作机制

  • 在React中,每个函数组件都对应一个Fiber节点,而Hook的状态信息是存储在这个Fiber节点上的。当你在函数组件中调用Hook时,React会在当前组件的Fiber节点中创建一个与Hook相关的数据结构来存储Hook的状态信息。

  • 具体来说,React使用一个链表(或树)来存储组件中所有的Hook状态。每个Hook都会被转换成一个单独的数据结构,然后按照调用顺序连接起来,形成一个链表。这个链表的头节点保存在Fiber节点的数据结构中,React通过这个链表来跟踪每个Hook的状态以及它们的调用顺序。

  • 当组件重新渲染时,React会重新执行函数组件,并根据Fiber节点中保存的Hook链表来恢复每个Hook的状态。这样可以确保在每次渲染时,Hook的状态都能正确地被恢复,并且能够保持Hook调用顺序的稳定性。

  • 需要注意的是,Hook的状态信息是存储在组件的Fiber节点中的,而不是存储在组件的实例中。这意味着即使函数组件被多次调用,每次调用都会创建一个新的Fiber节点来保存Hook的状态信息,从而保证了Hook状态的隔离和独立性。

示例

首先,我们创建一个名为useState.js的模块,用于模拟useState Hook的工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// useState.js

let hooks = [];
let currentHook = 0;

export function useState(initialState) {
hooks[currentHook] = hooks[currentHook] || initialState;
const hookIndex = currentHook; // 当前Hook的索引

function setState(newState) {
hooks[hookIndex] = newState; // 更新对应索引的Hook状态
// 模拟重新渲染
render();
}

return [hooks[currentHook++], setState];
}

然后,我们创建一个名为useEffect.js的模块,用于模拟useEffect Hook的工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// useEffect.js

let effects = [];
let currentEffect = 0;

export function useEffect(callback, deps) {
const effectIndex = currentEffect; // 当前Effect的索引
const prevDeps = effects[effectIndex] ? effects[effectIndex].deps : null;

// 判断依赖数组是否发生变化
const hasChangedDeps = !prevDeps || !deps.every((dep, i) => dep === prevDeps[i]);

// 如果依赖数组发生变化,执行回调函数
if (hasChangedDeps) {
callback();
}

// 更新依赖数组
effects[effectIndex] = { deps };

// 记录当前Effect的索引
currentEffect++;
}

最后,我们创建一个名为App.js的组件,使用我们自定义的useState和useEffect Hook:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// App.js

import { useState } from './useState';
import { useEffect } from './useEffect';

export function App() {
const [count, setCount] = useState(0);

useEffect(() => {
console.log('Component has been mounted or count has changed:', count);
}, [count]);

return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

喜欢这篇文章?打赏一下支持一下作者吧!
【react】为什么说只能在最顶层使用 Hook
https://www.cccccl.com/20240308/react/为什么说只能在最顶层使用 Hook/
作者
Jeffrey
发布于
2024年3月8日
许可协议