为什么`Promise.then`在React组件中被调用两次,而不是在console.log中?
我对以下组件的输出感到非常困惑:
import { StrictMode } from "react"
import ReactDOM from "react-dom"
function Test(): React.ReactElement {
console.log('render')
Promise.resolve()
.then(() => console.log('then ' + Math.random()))
return <></>
}
ReactDOM.render(
<StrictMode>
<Test />
</StrictMode>,
document.getElementById("root")
)
它至少在 Chrome 和 Firefox 中产生以下输出:
00:46:30.264 render
00:46:30.267 then 0.5430663800781927
00:46:30.267 then 0.9667426372511254
我宁愿期望看到相同数量的消息。我错过了什么?
重现:https : //codesandbox.io/s/elegant-frost-dmcsl
编辑:我知道严格模式会导致额外的渲染,但如上所述,我希望消息数量相同。
编辑 2:下面的两个答案都很棒。我想在这里引用@user56reinstatemonica8 的评论:
相关:关于控制台静音的社区反馈
回答
在 React严格模式下, react 可能会运行多个渲染时间,这可能部分解释你所看到的。
但是您正确地想知道是否是这种情况并且 render 被多次调用,为什么也render没有打印两次?
console.log()在某些情况下,React 会修改控制台方法,例如使日志静音。这是一个报价:
从 React 17 开始,React 自动修改控制台方法,如 console.log() 以在第二次调用生命周期函数时使日志静音。但是,在可以使用变通方法的某些情况下,它可能会导致不希望的行为。
显然,当console.log从 Promise 回调中调用时,它不会这样做。但是当它从渲染中调用时它会这样做。更多细节在@trincot 的回答中。
- @Bergi 这实际上是随机框架不应该接触系统函数(通常用于调试)来做意想不到的事情的原因。
- ……这也是为什么你不应该从渲染方法内部安排异步回调(或者更糟的是,启动异步工作),而只能从效果——一个效果只会运行一次。
回答
当启用严格模式(仅在开发模式下)时,您的渲染函数会再次运行,但正如此处所讨论的,React 会在第二次(同步)运行期间对console方法(调用disableLogs();)进行猴子修补,因此它不会输出。
该更新日志显示了这个代码被插入packages/react-reconciler/src/ReactFiberBeginWork.js,以暂时抑制原木(标有注释插入):
if (__DEV__) {
ReactCurrentOwner.current = workInProgress;
setIsRendering(true);
nextChildren = renderWithHooks(
current,
workInProgress,
render,
nextProps,
ref,
renderExpirationTime,
);
if (
debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictMode
) {
disableLogs(); // <--
try { // <--
nextChildren = renderWithHooks(
current,
workInProgress,
render,
nextProps,
ref,
renderExpirationTime,
);
} finally { // <--
reenableLogs(); // <--
} // <--
这是您的代码的一个版本,它演示了它确实运行了两次:
var i = 0;
var myconsolelog = console.log; // Work around React's monkeypatching
function Test(): React.ReactElement {
i++;
myconsolelog(i + ". render"); // will output twice now!
Promise.resolve(i)
.then((i) => console.log(i + ". then " + Math.random()));
return <></>;
}
在我看来,这种日志抑制是一个非常糟糕的设计选择。
- 是的...不知道为什么他们觉得需要对控制台方法进行猴子补丁。我知道这第二次渲染可能是一个惊喜......但恕我直言,最好写一条调试消息说“开始组件 X 的第二次渲染,请参阅 `https://blah` 了解为什么会在开发模式下发生这种情况” ,即他们应该**添加**他们对为什么日志重复而不是使它们静音的解释......鉴于用户可以通过多种其他方式检测双重渲染,因此实际上没有意义把它藏起来...
- 相关:[关于控制台静音的社区反馈](https://github.com/facebook/react/issues/21783)
- 是的,或者为了减少噪音,在第二个渲染周期开始时,他们可以做类似`console.groupCollapsed('Starting dev-only second render, see [link] for info')`,运行`console.groupEnd`最后,并在折叠的手风琴中捕获所有二次渲染日志消息。最小的噪音,没有隐藏的惊喜,清楚发生了什么。
- 是的,在这个 [changelog](https://github.com/facebook/react/pull/18547/files/b7e2345755e07c1174b3a755a78f36a6df7fa827) 中看到,并看到对 `disableLogs` 和 `reenableLogs` 和 `render` 的调用。添加信息以回答。