为什么在 React 组件中调用了两次 `Promise.then` 而不是在 console.log 中?

IT技术 javascript reactjs promise
2021-05-06 10:00:21

我对以下组件的输出感到非常困惑:

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 的评论:

相关:关于控制台静音的社区反馈

3个回答

在阵营严格的模式react可以运行呈现时间,这可能部分解释你所看到的。

但是您正确地想知道是否是这种情况并且 render 被多次调用,为什么也render没有打印两次

console.log()在某些情况下,React 会修改控制台方法,例如使日志静音。这是一个报价:

从 React 17 开始,React 自动修改控制台方法,如 console.log() 以在第二次调用生命周期函数时使日志静音。但是,在可以使用变通方法的某些情况下,它可能会导致不希望的行为。

显然,当console.log从 Promise 回调中调用时,它不会这样做但是当它从渲染中调用时它会这样做。更多细节在@trincot 的回答中。

当启用严格模式(仅在开发模式下)时,您的渲染函数会再次运行,但正如此处所讨论的,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 <></>;
}

在我看来,这种日志抑制是一个非常糟糕的设计选择。

如果它可能对任何人有帮助,您可以使用猴子补丁Object.defineProperties来忽略对console对象所做的任何更改,从而有效地防止 ReactDOM 被猴子补丁console.log

const defineProperties = Object.defineProperties;
Object.defineProperties = function (o, props) {
  return o === console ? o : defineProperties(o, props);
};

确保仅将其置于开发模式(例如在 create-react-app when 中process.env.NODE_ENV === 'development'),以便它不会在生产构建中结束。