我正在阅读React钩子的概念。我通过了一条规则,上面写着Don't call React hooks inside conditions
。在这里,他们提供了解释链接。
function Form() {
// 1. Use the name state variable
const [name, setName] = useState('Mary');
// 2. Use an effect for persisting the form
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
// 3. Use the surname state variable
const [surname, setSurname] = useState('Poppins');
// 4. Use an effect for updating the title
useEffect(function updateTitle() {
document.title = name + ' ' + surname;
});
// ...
}
我理解他们想说什么,但我不知道确切的原因,比如为什么我不能在if-else块中使用useEffect?
还有一条语句
So how does React know which state corresponds to which useState call?
useState每次都是不同的调用,它可以返回新的";[state,setState]";每一次,那么这里有什么难知道的是谁调用了哪个useState?
这与谁调用了哪个钩子useXXXX
(即useState
、useEffect
等)无关。它是关于钩子如何在内部实现并与每个组件关联的。还有很多其他问题需要解决,React依赖于钩子的调用顺序。
来自文档挂钩常见问题部分
React如何将Hook调用与组件关联起来
每个组件都有一个内部的"存储单元"列表。它们只是JavaScript对象,我们可以在其中放置一些数据。当您调用类似于useState()的Hook时,会读取当前单元格(或在第一次渲染时对其进行初始化),然后将指针移动到下一个单元格。这就是多个useState()调用每个get独立本地状态的方式。
内部钩子像队列一样实现,其中每个钩子代表一个引用下一个的节点。内部结构可能与此类似,
{
memoizedState: 'a',
next: {
memoizedState: 'b',
next: null
}
}
以具有4个状态变量为例,调用useState
4次。对于每个钩子调用,如果值尚未初始化(即第一次渲染时),它将初始化从内存单元读取的其他值,然后在内部移动到下一个钩子。
// 4 independent local state variables with their own "memory cell"
// nothing is called conditionally so the call order remains the same
// across renders
useState(1) // 1st call
useState(2) // 2nd call
useState(3) // 3rd call
useState(4) // 4th call
useState(1)
if (condition) { // if condition false hook call will be skipped
useState(2)
}
useState(3)
useState(4)
现在,如果条件是false
,那么当您有条件地调用钩子时,钩子调用将被跳过。这意味着每个后续的hook调用将在调用顺序中偏移1,导致无法读取状态值或替换效果,或者更难检测到错误。
所以一般来说,有条件地调用任何钩子都是个坏主意。只调用顶级中的钩子(不在条件、嵌套函数或循环中),这将有助于React为多个钩子调用保留钩子的状态。
基本上,钩子依赖于调用索引。React不知道给定的useState()
在上一次渲染时返回了什么状态,但它知道该组件对useState()
的第一次调用返回了[1,2]
作为其值,第二次调用返回false
。现在,如果react唯一知道的是给定调用索引的给定返回是什么,那么如果我能写一个这样的组件,你认为会发生什么:
const [a, setA] = React.useState([1,2,3]);
let c;
if(a === [3,2,1]){
c = React.useState('X');
}
const [b, setB] = React.useState(false);
React.useEffect(() => setA([3,2,1]), []);
现在,react从第一个渲染中知道第一个调用返回[1,2,3],第二个返回false。然后效果重新渲染组件,现在它不是第一个渲染,所以第一个调用将返回更新后的状态[3,2,1]
,第二个调用(c = ...
)将返回false,但随后react看到第三个调用,它应该返回什么?
从react的角度来看,这毫无意义,从你的角度来看这可能会导致大量的错误和问题。
当然,我的基本解释和React的解释都不多,这就是为什么一个有消息来源的Dan Abramov,React的一名工作人员在他的博客上有一篇关于这件事的非常长和详细的帖子,你可以在这里找到。他还发布了很多关于窗帘后面的反应的其他内容,值得一读。
从React文档的这个答案中,它提到钩子存储在"memory cells"
中,并按顺序呈现(" moves the pointer to the next one"
)
有一个内部的"内存单元"列表组成部分它们只是JavaScript对象,我们可以在其中放置一些数据。当您调用类似useState()的Hook时,它读取当前单元格(或在第一次渲染期间初始化它),然后将指针移动到下一个。这就是多个useState()调用每个get的方式独立的地方政府。
这与您提供的链接的下面部分相匹配,该部分有更多的解释
//第一次渲染//---------------
useState('Mary')//1。使用"Mary"初始化名称状态变量
useEffect(persistForm)//2。添加用于持久化表单的效果
useState('Poppins')//3。使用"Poppins"初始化姓氏状态变量
useEffect(updateTitle)//4。添加更新标题的效果
//---------------//第二次渲染//---------------
useState('Mary')//1。读取名称状态变量(参数被忽略)
useEffect(persistForm)//2。替换持久化的效果形成
useState('Poppins')//3。读取姓氏状态变量(参数被忽略)
useEffect(updateTitle)//4。更换更新标题的效果
在第二个渲染部分中,docs说Read the ... variable
意味着当useState
在第二次调用时,它不会生成新的[state,setState],而是来到"memory cells"
读取状态值并返回,然后我们通过const [state, setState] = useEffect()
将其分配给新的数组。这就是为什么React可以保证每次重新渲染时setState
不会发生变化
React保证setState函数标识是稳定的,不会重新渲染时的更改。这就是为什么从useEffect中省略是安全的或使用回调依赖关系列表。