useEffect Hook 示例:什么导致重新渲染?
useEffect Hook Example: What causes the re-render?
我想知道 useEffect 何时会导致重新渲染。我对以下示例的结果感到非常惊讶:
https://codesandbox.io/embed/romantic-sun-j5i4m
function useCounter(arr = [1, 2, 3]) {
const [counter, setCount] = useState(0);
useEffect(() => {
for (const i of arr) {
setCount(i);
console.log(counter);
}
}, [arr]);
}
function App() {
useCounter();
console.log("render");
return <div className="App" />;
}
本例结果如下:
不知道为什么:
- 该组件只渲染 3 次(我猜组件会在每次调用
setCount
时重新渲染 + 一次初始渲染 - 所以 4 次)
- 计数器只有两个值 0 和 3:我猜,正如 article 所述,每个渲染器都会看到自己的状态和道具,因此整个循环将 运行 每个状态作为常量 (1, 2, 3) --> 但为什么状态永远不会是 2?
setState 和类似的钩子不会立即重新渲染您的组件。他们可能会分批更新或推迟更新。因此,在 counter === 3
.
的最新 setCount
之后,您只会得到一个重新渲染
您使用 counter === 0
进行初始渲染,使用 counter === 3
进行两次额外的重新渲染。我不确定为什么它不会进入无限循环。 arr = [1, 2, 3]
应该在每次调用时创建一个新数组并触发 useEffect
:
- 初始渲染集
counter
到 0
useEffect
记录 0
三次,将 counter
设置为 3
并触发重新渲染
- 首先使用
counter === 3
重新渲染
useEffect
记录 3
三次,将 counter
设置为 3
和 ???
React 要么在此处停止,要么从第 3 步开始无限循环。
我将尽力解释(或演练)正在发生的事情。我还在第 7 点和第 10 点做出两个假设。
- 应用组件挂载。
useEffect
挂载后调用
useEffect
将 'save' 初始状态,因此 counter
将在其中引用时为 0。
- 循环运行s 3次。每次迭代
setCount
都被调用来更新计数,并且控制台日志记录根据 'stored' 版本为 0 的计数器。因此数字 0 在控制台中记录了 3 次。因为状态已经改变 (0 -> 1, 1 -> 2, 2 -> 3) React 设置为一个标志或其他东西来告诉自己记住 re-render.
- React 在
useEffect
的执行期间没有 re-render 编辑任何东西,而是等到 useEffect
完成到 re-render。
- 一旦
useEffect
完成,React 会记住 counter
的状态在其执行过程中发生了变化,因此它将 re-render App.
- 应用程序 re-renders 和
useCounter
被再次调用。注意这里没有参数传递给 useCounter
自定义挂钩。
假设: 我自己也不知道,但我认为默认参数似乎又被创建了,或者至少以一种让 React 认为它是新的方式.因此,由于 arr
被视为新的,因此 useEffect
挂钩将再次 运行。这是我可以再次解释 useEffect
运行ning 的唯一原因。
- 在
useEffect
的第二个 运行 期间,counter
的值为 3。因此控制台日志将按预期记录 3 次数字 3。
- 在
useEffect
第二次 运行 之后,React 发现计数器在执行期间发生了变化(3 -> 1、1 -> 2、2 -> 3),因此 App将 re-render 导致第三个 'render' 日志。
- 假设: 因为
useCounter
钩子的内部状态从App,它不执行其中的代码,因此 useEffect
不会被第三次调用。因此,应用程序的第一次呈现将始终 运行 挂钩代码。第二次 App 看到钩子的内部状态将其 counter
从 0 更改为 3,因此决定 re-run 它,第三次 App 看到内部状态为 3 并且仍然是3 所以它决定不 re-run 它。这是我能想到的让钩子不再 运行 的最好理由。您可以在挂钩本身内放置一个日志,以查看它不会第三次影响 运行。
这是我看到的情况,我希望这能让它更清楚一些。
我在 React 文档中找到了第三次渲染的 explanation。我认为这澄清了为什么在不应用效果的情况下进行第三次渲染的反应:
If you update a State Hook to the same value as the current state,
React will bail out without rendering the children or firing effects.
(React uses the Object.is comparison algorithm.)
Note that React may still need to render that specific component again
before bailing out. That shouldn’t be a concern because React won’t
unnecessarily go “deeper” into the tree. If you’re doing expensive
calculations while rendering, you can optimize them with useMemo.
似乎 useState 和 useReducer 共享这个救助逻辑。
有一个巧合可能会在原始问题中造成一些混乱。主要是因为有 3 个渲染并且 useCounter
的默认参数长度等于 3。在下面你可以看到即使对于更大的数组也只有 3 个渲染。
function useCounter(arr = [1, 2, 3, 4 , 5 , 6]) {
const [counter, setCount] = React.useState(0);
React.useEffect(() => {
for (const i of arr) {
setCount(i);
console.log(counter);
}
}, [arr]);
}
function App() {
useCounter();
console.log("render");
return <div className = "App" / > ;
}
ReactDOM.render( <App /> ,
document.getElementById("root")
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
另一个混淆可能是因为 setState
每次都被调用,除了第一个,具有相同的值(数组的最后一个值),这实际上取消了渲染。但是,如果使用不同的值调用 setState
,则呈现的流程将创建一个无限循环 :)
因为每隔一个 render
触发一个 useEffect
,然后触发一个 setSate
,然后触发一个 render
,然后触发一个 useEffect
,依此类推。
希望这能让一些人更清楚。
以上解决方案很好地解释了代码中发生的事情。如果有人正在寻找如何在自定义挂钩中使用默认参数时避免 re-renders 。这是一个可能的解决方案。
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const defaultVal = [1, 2, 3];
function useCounter(arr = defaultVal) {
const [counter, setCount] = useState(0);
useEffect(() => {
console.log(counter);
setCount(arr);
}, [counter, arr]);
return counter;
}
function App() {
const counter = useCounter();
console.log("render");
return (
<div className="App">
<div>{counter}</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
说明:由于没有向自定义挂钩提供任何值,因此它采用常量 defaultVal
的默认值。这意味着 arr
引用总是相同的。由于引用未更改,因此不会触发 useEffect 挂钩
这个问题和我读到的所有答案都非常有见地,可以更好地理解 useEffect 和 useState 挂钩,因为它们迫使我深入研究以深入掌握它们。
虽然@ApplePearPerson 的回答非常巧妙,但我确实相信有一些不正确的方面,我会用几个例子指出它们:
组件已渲染,因此控制台中的第一个“渲染”。
UseEffect 运行 总是至少一个,在第一次渲染之后,这基本上
解释第二个渲染,这是为什么首先打印的棘手部分
0 x(计数器的初始值)
useState 钩子的第二个参数是一个异步函数,因此具有异步行为:它等待其他代码到 运行,所以它等待 for in 块到 运行 .
因此块 运行s 中的 for 等等:
i 从 1 到 3,完成值为 3
此时setCount改变计数器从0到3
Useffect 运行s on dependencies change if there are the array as second argument, 所以在这种情况下,即使它不包括在内,它 运行s on counter 也就是已从 setCount 更改,您甚至可以从 Eslint 警告中看到(React Hook useEffect 缺少依赖项:'counter')
useState 改变状态导致 hook 一个渲染(这就是为什么引入 useRef 来改变 dom 元素而不会导致重新渲染),尽管情况并非总是如此class中的setState(但这是另一个话题)
最后一次渲染是因为在每次渲染时都会重新创建 arr,因为
ApplePearPerson“注意到”但是作为组件是一个全新的数组
已重新渲染,但计数器为 3,与
我拥有的最后一个值,也正好是 3,所以 useEffect
不会再 运行。
这张截图可以帮助可视化我的总结
因此,例如,如果我们将 for of 更改为 for in,这意味着我们获取数组的键(即字符串),在这种情况下我们会看到计数器的最后一个值为 2
https://codesandbox.io/s/kind-surf-oq02y?file=/src/App.js
可以添加第二个计数器来完成另一个测试,该计数器设置为前一个。
在这种情况下,我们获得了第四个渲染,因为 count2 在 1 个 useEffect 之后并且它从 0 变为 3 触发最后一个渲染而不是最后一个 useEffect 运行.
总结一下:
有 3 个渲染:
首先是由于组件第一次挂载。
第二次是由于第一次渲染后的 useEffect 运行。
第三个是由于依赖项从 0 更改为 3
https://codesandbox.io/s/kind-surf-oq02y?file=/src/App.js:362-383
我想知道 useEffect 何时会导致重新渲染。我对以下示例的结果感到非常惊讶:
https://codesandbox.io/embed/romantic-sun-j5i4m
function useCounter(arr = [1, 2, 3]) {
const [counter, setCount] = useState(0);
useEffect(() => {
for (const i of arr) {
setCount(i);
console.log(counter);
}
}, [arr]);
}
function App() {
useCounter();
console.log("render");
return <div className="App" />;
}
本例结果如下:
不知道为什么:
- 该组件只渲染 3 次(我猜组件会在每次调用
setCount
时重新渲染 + 一次初始渲染 - 所以 4 次) - 计数器只有两个值 0 和 3:我猜,正如 article 所述,每个渲染器都会看到自己的状态和道具,因此整个循环将 运行 每个状态作为常量 (1, 2, 3) --> 但为什么状态永远不会是 2?
setState 和类似的钩子不会立即重新渲染您的组件。他们可能会分批更新或推迟更新。因此,在 counter === 3
.
setCount
之后,您只会得到一个重新渲染
您使用 counter === 0
进行初始渲染,使用 counter === 3
进行两次额外的重新渲染。我不确定为什么它不会进入无限循环。 arr = [1, 2, 3]
应该在每次调用时创建一个新数组并触发 useEffect
:
- 初始渲染集
counter
到0
useEffect
记录0
三次,将counter
设置为3
并触发重新渲染- 首先使用
counter === 3
重新渲染
useEffect
记录3
三次,将counter
设置为3
和 ???
React 要么在此处停止,要么从第 3 步开始无限循环。
我将尽力解释(或演练)正在发生的事情。我还在第 7 点和第 10 点做出两个假设。
- 应用组件挂载。
useEffect
挂载后调用useEffect
将 'save' 初始状态,因此counter
将在其中引用时为 0。- 循环运行s 3次。每次迭代
setCount
都被调用来更新计数,并且控制台日志记录根据 'stored' 版本为 0 的计数器。因此数字 0 在控制台中记录了 3 次。因为状态已经改变 (0 -> 1, 1 -> 2, 2 -> 3) React 设置为一个标志或其他东西来告诉自己记住 re-render. - React 在
useEffect
的执行期间没有 re-render 编辑任何东西,而是等到useEffect
完成到 re-render。 - 一旦
useEffect
完成,React 会记住counter
的状态在其执行过程中发生了变化,因此它将 re-render App. - 应用程序 re-renders 和
useCounter
被再次调用。注意这里没有参数传递给useCounter
自定义挂钩。 假设: 我自己也不知道,但我认为默认参数似乎又被创建了,或者至少以一种让 React 认为它是新的方式.因此,由于arr
被视为新的,因此useEffect
挂钩将再次 运行。这是我可以再次解释useEffect
运行ning 的唯一原因。 - 在
useEffect
的第二个 运行 期间,counter
的值为 3。因此控制台日志将按预期记录 3 次数字 3。 - 在
useEffect
第二次 运行 之后,React 发现计数器在执行期间发生了变化(3 -> 1、1 -> 2、2 -> 3),因此 App将 re-render 导致第三个 'render' 日志。 - 假设: 因为
useCounter
钩子的内部状态从App,它不执行其中的代码,因此useEffect
不会被第三次调用。因此,应用程序的第一次呈现将始终 运行 挂钩代码。第二次 App 看到钩子的内部状态将其counter
从 0 更改为 3,因此决定 re-run 它,第三次 App 看到内部状态为 3 并且仍然是3 所以它决定不 re-run 它。这是我能想到的让钩子不再 运行 的最好理由。您可以在挂钩本身内放置一个日志,以查看它不会第三次影响 运行。
这是我看到的情况,我希望这能让它更清楚一些。
我在 React 文档中找到了第三次渲染的 explanation。我认为这澄清了为什么在不应用效果的情况下进行第三次渲染的反应:
If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.
似乎 useState 和 useReducer 共享这个救助逻辑。
有一个巧合可能会在原始问题中造成一些混乱。主要是因为有 3 个渲染并且 useCounter
的默认参数长度等于 3。在下面你可以看到即使对于更大的数组也只有 3 个渲染。
function useCounter(arr = [1, 2, 3, 4 , 5 , 6]) {
const [counter, setCount] = React.useState(0);
React.useEffect(() => {
for (const i of arr) {
setCount(i);
console.log(counter);
}
}, [arr]);
}
function App() {
useCounter();
console.log("render");
return <div className = "App" / > ;
}
ReactDOM.render( <App /> ,
document.getElementById("root")
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
另一个混淆可能是因为 setState
每次都被调用,除了第一个,具有相同的值(数组的最后一个值),这实际上取消了渲染。但是,如果使用不同的值调用 setState
,则呈现的流程将创建一个无限循环 :)
因为每隔一个 render
触发一个 useEffect
,然后触发一个 setSate
,然后触发一个 render
,然后触发一个 useEffect
,依此类推。
希望这能让一些人更清楚。
以上解决方案很好地解释了代码中发生的事情。如果有人正在寻找如何在自定义挂钩中使用默认参数时避免 re-renders 。这是一个可能的解决方案。
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const defaultVal = [1, 2, 3];
function useCounter(arr = defaultVal) {
const [counter, setCount] = useState(0);
useEffect(() => {
console.log(counter);
setCount(arr);
}, [counter, arr]);
return counter;
}
function App() {
const counter = useCounter();
console.log("render");
return (
<div className="App">
<div>{counter}</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
说明:由于没有向自定义挂钩提供任何值,因此它采用常量 defaultVal
的默认值。这意味着 arr
引用总是相同的。由于引用未更改,因此不会触发 useEffect 挂钩
这个问题和我读到的所有答案都非常有见地,可以更好地理解 useEffect 和 useState 挂钩,因为它们迫使我深入研究以深入掌握它们。
虽然@ApplePearPerson 的回答非常巧妙,但我确实相信有一些不正确的方面,我会用几个例子指出它们:
组件已渲染,因此控制台中的第一个“渲染”。
UseEffect 运行 总是至少一个,在第一次渲染之后,这基本上 解释第二个渲染,这是为什么首先打印的棘手部分 0 x(计数器的初始值)
useState 钩子的第二个参数是一个异步函数,因此具有异步行为:它等待其他代码到 运行,所以它等待 for in 块到 运行 .
因此块 运行s 中的 for 等等:
i 从 1 到 3,完成值为 3
此时setCount改变计数器从0到3
Useffect 运行s on dependencies change if there are the array as second argument, 所以在这种情况下,即使它不包括在内,它 运行s on counter 也就是已从 setCount 更改,您甚至可以从 Eslint 警告中看到(React Hook useEffect 缺少依赖项:'counter')
useState 改变状态导致 hook 一个渲染(这就是为什么引入 useRef 来改变 dom 元素而不会导致重新渲染),尽管情况并非总是如此class中的setState(但这是另一个话题)
最后一次渲染是因为在每次渲染时都会重新创建 arr,因为 ApplePearPerson“注意到”但是作为组件是一个全新的数组 已重新渲染,但计数器为 3,与 我拥有的最后一个值,也正好是 3,所以 useEffect 不会再 运行。
这张截图可以帮助可视化我的总结
因此,例如,如果我们将 for of 更改为 for in,这意味着我们获取数组的键(即字符串),在这种情况下我们会看到计数器的最后一个值为 2
https://codesandbox.io/s/kind-surf-oq02y?file=/src/App.js
可以添加第二个计数器来完成另一个测试,该计数器设置为前一个。 在这种情况下,我们获得了第四个渲染,因为 count2 在 1 个 useEffect 之后并且它从 0 变为 3 触发最后一个渲染而不是最后一个 useEffect 运行.
总结一下:
有 3 个渲染:
首先是由于组件第一次挂载。
第二次是由于第一次渲染后的 useEffect 运行。
第三个是由于依赖项从 0 更改为 3
https://codesandbox.io/s/kind-surf-oq02y?file=/src/App.js:362-383