React 是否有可能修改引用?

blu*_*e10 8 javascript reactjs

我做了一个在 React (*) 中手动实现观察者模式的小实验。它基本上有效,但有一个非常出乎意料的细节。考虑这个最小的例子:

class Observer {
  constructor() {
    this.callbacks = [];
  }

  register(callback) {
    console.log("received callback register");
    this.callbacks.push(callback);
    console.log(`number of callbacks: ${this.callbacks.length}`);
  }

  call() {
    console.log(`calling ${this.callbacks.length} callbacks`);
    for (let callback of this.callbacks) {
      callback();
    }
  }
}

function Main() {
  const observer = useRef(new Observer());

  useEffect(() => {
    observer.current.call();
  }, [observer]);

  return <SubComponent observer={observer.current} />;
}

function SubComponent({ observer }) {
  console.log("registering observer");
  observer.register(() => {
    console.log("callback called");
  });
  return <div>Hello World</div>;
}
Run Code Online (Sandbox Code Playgroud)

代码沙盒

在控制台日志中,这会产生:

registering observer
received callback register
number of callbacks: 1
calling 2 callbacks
callback called
callback called
Run Code Online (Sandbox Code Playgroud)

如您所见,注册回调的数量突然变为 2,即使仅注册了 1 个回调。这怎么可能?我是否有盲点,或者这是否暗示了 React 的工作方式?


(*)我知道,这个问题可以通过一个被解决的组合useImperativeHandle和forwardRef。以上只是一个研究替代方案的实验,我要求的是学习目的。

Den*_*ash 6

因为您将注册逻辑放在 render 函数(函数组件的主体)中,所以它会在每个组件的 render上注册它。

因为你有StrictModewrapper,它调用了两次:

严格模式无法自动为您检测副作用,但它可以通过使它们更具确定性来帮助您发现它们。这是通过有意重复调用以下函数来完成的:

  • ...
  • 函数组件体

您可以删除StrictMode(不推荐)或编写逻辑,useEffect因为我想您希望它在observer更改时注册:

function Main() {
  const observer = useRef(new Observer());

  useEffect(() => {
    observer.current.call();
  }, [observer]);

  return <SubComponent observer={observer.current} />;
}
Run Code Online (Sandbox Code Playgroud)

编辑患者缓存emkoo

请注意,在 StrictMode 中,日志被静音,因此您看不到第二个 console.log("registering observer");

从 React 17 开始,React 会自动修改控制台方法,例如console.log()在第二次调用生命周期函数时将日志静音。

  • 但该组件只渲染一次,不是吗?如果函数体被评估两次,我不应该看到多个“console.log”消息吗? (2认同)
  • 在这种情况下,React 在第二次渲染中禁用日志,请检查[this](https://github.com/facebook/react/blob/0db61a08befe6406aa93568708224d1cca2aff7d/packages/shared/ConsolePatchingDev.js) (2认同)
  • @Zac 这解释了很多,谢谢!所以让我困惑的是“console.log”不再是 React 中的普通“console.log”(我猜是开发模式)。 (2认同)