相关文章
为什么说只能在最顶层使用 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
|
let hooks = []; let currentHook = 0;
export function useState(initialState) { hooks[currentHook] = hooks[currentHook] || initialState; const hookIndex = currentHook;
function setState(newState) { hooks[hookIndex] = newState; 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
|
let effects = []; let currentEffect = 0;
export function useEffect(callback, deps) { const effectIndex = currentEffect; const prevDeps = effects[effectIndex] ? effects[effectIndex].deps : null;
const hasChangedDeps = !prevDeps || !deps.every((dep, i) => dep === prevDeps[i]);
if (hasChangedDeps) { callback(); }
effects[effectIndex] = { deps };
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
|
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> ); }
|