为啥在 React 组件中调用了两次 Promise.then 而不是 console.log?
Posted
技术标签:
【中文标题】为啥在 React 组件中调用了两次 Promise.then 而不是 console.log?【英文标题】:Why is `Promise.then` called twice in a React component but not the console.log?为什么在 React 组件中调用了两次 Promise.then 而不是 console.log? 【发布时间】:2021-09-18 08:18:59 【问题描述】:我对以下组件的输出感到非常困惑:
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 的评论:
相关:Community feedback on console silencing
【问题讨论】:
请添加所有的代码来重现此问题,而不仅仅是沙箱的链接。<StrictMode>
实际上是您问题的关键。
是的,但为什么render
只打印一次?
【参考方案1】:
在 React strict mode 中,react 可能会运行渲染多次次,这可以部分解释你所看到的。
但您正确地想如果是这样并且多次调用 render,为什么render
也没有打印两次?
在某些情况下,React 会修改 console.log()
等控制台方法以使日志静音。这是一个引用:
从 React 17 开始,React 自动修改控制台 像 console.log() 这样的方法在第二次调用中使日志静音 生命周期函数。但是,它可能会导致不良行为 在某些情况下可以使用变通方法。
显然,当从 Promise 回调调用 console.log
时,它不会这样做。但是当它从渲染中调用时它会这样做。 @trincot 的回答中有更多详细信息。
【讨论】:
...这也是为什么你不应该从渲染方法内部安排异步回调(或者更糟糕的是,启动异步工作),而只能从效果中 - 效果只会运行一次。 @Bergi 这就是为什么随机框架不应该接触系统函数(通常用于调试)来做意想不到的事情的原因。【参考方案2】:当启用严格模式(仅在开发模式下)时,您的渲染函数会第二次运行,但正如 here 所讨论的,React 将在此期间修补 console
方法(调用 disableLogs();
)第二次(同步)运行,使其不输出。
changelog 显示此代码已插入 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 <></>;
在我看来,这种日志抑制是一个非常糟糕的设计选择。
【讨论】:
是的,在这个changelog 中看到,在renderWithHooks
的调用周围看到disableLogs
和reenableLogs
对。添加信息以回答。
是的...不知道为什么他们觉得需要猴子补丁控制台方法。我知道这第二次渲染可能是一个惊喜......但恕我直言,最好写一条调试消息“开始对组件 X 进行第二次渲染,请参阅https://blah
了解为什么会在开发模式下发生这种情况”,即他们应该添加了他们对为什么日志重复而不是使它们静音的解释......鉴于用户可以检测到双重渲染的许多其他方式,隐藏它并没有真正意义它...
是的,或者为了减少噪音,在第二个渲染周期开始时,他们可以执行console.groupCollapsed('Starting dev-only second render, see [link] for info')
之类的操作,最后运行console.groupEnd
,然后捕获所有第二个渲染日志消息折叠的手风琴。最小的噪音,没有隐藏的惊喜,清楚发生了什么。
相关:Community feedback on console silencing
我同意,这个猴子补丁是一个糟糕的选择。也许,他们的开发人员经常使用console.log
? ;)【参考方案3】:
如果它可能对任何人有所帮助,您可以对 Object.defineProperties
进行猴子修补以忽略对 console
对象所做的任何更改,从而有效地防止 ReactDOM 对 console.log
进行猴子修补。
const defineProperties = Object.defineProperties;
Object.defineProperties = function (o, props)
return o === console ? o : defineProperties(o, props);
;
确保仅将其置于开发模式(例如,process.env.NODE_ENV === 'development'
时置于 create-react-app 中),这样它就不会在生产构建中结束。
【讨论】:
以上是关于为啥在 React 组件中调用了两次 Promise.then 而不是 console.log?的主要内容,如果未能解决你的问题,请参考以下文章
为啥在 Swift 中 CollectionView 函数被调用了两次?
didUpdateToLocation 调用了两次,好的。为啥 oldLocation 两次都为零?