当前位置: 首页 > 知识库问答 >
问题:

为什么在顶层调用反应挂钩?

皮弘博
2023-03-14

我在读《新概念》。我遇到了一条规则,上面说不要在条件内调用React hook。在这里,他们提供了解释链接。

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块中使用useffect?

还有一种说法

那么React如何知道哪个状态对应于哪个useState调用呢?

useState每次都是不同的调用,每次都可以返回新的“[state,setState]”,所以这里有什么困难知道谁调用了哪个useState?

共有3个答案

易祯
2023-03-14

从React文档的回答中,它提到钩子存储在的“内存单元”中,并按顺序呈现(“将指针移动到下一个”

每个组件都有一个内部“存储单元”列表。它们只是JavaScript对象,我们可以在其中放置一些数据。当调用像useState()这样的钩子时,它会读取当前单元格(或在第一次渲染时对其进行初始化),然后将指针移动到下一个单元格。这就是多个useState()调用每个获取独立本地状态的方式。

与您提供的链接的下面部分相匹配,其中有更多的解释

//第一次渲染//------------

Mary)//1.用'玛丽'初始化名称状态变量

useffect(persistForm)//2。添加用于持久化表单的效果

useState('Poppins')//3。使用“Poppins”初始化姓氏状态变量

useffect(updateTitle)//4。添加用于更新标题的效果

//------//第二次渲染//-------------

useState('Mary')//1。读取名称状态变量(忽略参数)

USEPERP(持久形式)//2.替换持久化窗体的效果

useState('Poppins')//3。读取姓氏状态变量(忽略参数)

useffect(updateTitle)//4。替换用于更新标题的效果

在第二个渲染部分中,文档说,读取。。。变量意味着当第二次调用useState时,它不会生成新的[state,setState],而是进入的“内存单元”读取状态值并返回,然后我们通过常量[state,setState]=useEffect()将其分配给新数组。这就是为什么React可以保证每次重新渲染时不会更改setState

React保证setState函数标识是稳定的,并且在重新渲染时不会更改。这就是为什么从useEffect或useCallback依赖项列表中省略是安全的。

阎裕
2023-03-14

这不是关于谁调用了哪个钩子useXXXX(即useStateuseffect,等等)。它是关于钩子如何在内部实现以及如何与每个组件关联。还有许多其他问题需要解决,这取决于钩子的调用顺序。

从docs Hooks常见问题部分

React如何将钩子调用与组件相关联?

每个组件都有一个内部“存储单元”列表。它们只是JavaScript对象,我们可以在其中放置一些数据。当调用像useState()这样的钩子时,它会读取当前单元格(或在第一次渲染时对其进行初始化),然后将指针移动到下一个单元格。这就是多个useState()调用每个获取独立本地状态的方式。

内部钩子的实现类似于一个队列,其中每个钩子代表一个引用下一个节点的节点。内部结构可能与此类似,

{
  memoizedState: 'a',
  next: {
    memoizedState: 'b',
    next: null
  }
}

以调用useState4次来获得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,钩子调用将被跳过。这意味着每个后续的钩子调用将在调用顺序中移位1,导致无法读取状态值或替换效果或更难检测的错误。

所以一般来说,有条件地调用任何钩子都不是一个好主意。只在顶层调用钩子(而不是内部条件、嵌套函数或循环),这将有助于对多个钩子调用保持钩子的状态作出反应。

应俭
2023-03-14

基本上,钩子依赖于调用索引。React不知道给定的useState()返回了什么,因为它是前一个渲染的状态,但是它知道该组件对useState()的第一次调用返回了一个[1,2]作为它的值,第二次调用调用返回false。现在,如果唯一的反应知道的是给定的调用索引的给定回报,你认为会发生什么,如果我可以写这样的组件:

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的角度来看,这毫无意义,从您的角度来看,这可能会导致大量的bug和问题。

当然,我的基本解释和React都不多,这就是为什么有消息来源,React工作人员之一丹·阿布拉莫夫在他的博客上有一篇很长很详细的文章,你可以在这里找到。他还发布了很多关于react如何在幕后工作的其他内容,值得一读。

 类似资料:
  • 我是反应的初学者。我试图将我的数据库的值从一个文件发送到另一个使用反应挂钩,并得到以下错误有人能帮我吗? 第5:41行:不能在顶层调用React钩子“useState”。必须在React函数组件或自定义React钩子函数React钩子/钩子规则代码中调用React钩子:

  • 问题内容: 我正在尝试与大学的MySQL数据库建立连接,但该连接已挂起。 此调用:打印(在我最终杀死它之后): 我刚刚从下载的MySQL连接器/ J 这里。我不确定这是否是问题的一部分。我非常准确地遵循了指示。 我也可以像这样在命令行上连接到mysql: 可能的问题: 我写的Java代码 我如何安装MySQL Connector / J 某种网络问题阻止了连接 问题: 我应该怎么解决这个问题?为什

  • 问题内容: 什么是反射,为什么有用? 我对Java特别感兴趣,但是我认为原理在任何语言中都是相同的。 问题答案: 名称反射用于描述能够检查同一系统(或本身)中的其他代码的代码。 例如,假设您在Java中有一个未知类型的对象,并且想在该对象上调用“ doSomething”方法(如果存在)。除非对象符合已知的接口,否则Java的静态类型化系统并不是真正为支持该类型而设计的,但是使用反射,您的代码可以

  • 我在React hook中遇到了一些非常奇怪的行为。在下面的代码中(https://codesandbox.io/s/purple-bush-nb5uy?file=/src/index.js): 单击时,我们在控制台中获得以下序列: 我希望: 因为我认为如果React找到setter并在重新渲染后执行以下代码,它会立即重新渲染。此外,我的React应用程序也是如此: 当运行以及值与状态变量的内容不

  • 问题内容: 什么是java反射,为什么有用? 问题答案: 名称反射用于描述能够检查同一系统(或本身)中其他代码的代码。 例如,假设你在Java中有一个未知类型的对象,并且你想在该对象上调用“ doSomething”方法(如果存在)。除非对象符合已知的接口,否则Java的静态类型化系统并不是真正为支持该类型而设计的,但是使用反射,你的代码可以查看该对象并确定其是否具有名为“ doSomething