React是否有可能修改引用?

我做了一个在 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>;
}

代码沙盒

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

registering observer
received callback register
number of callbacks: 1
calling 2 callbacks
callback called
callback called

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


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

回答

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

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

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

  • ...
  • 函数组件体

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

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

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

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

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

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

  • But the component only gets rendered once, doesn't it? Shouldn't I see multiple `console.log` messages if the function body gets evaluated twice?
  • In this case, React disables logs in the second render,
    check [this](https://github.com/facebook/react/blob/0db61a08befe6406aa93568708224d1cca2aff7d/packages/shared/ConsolePatchingDev.js)
  • @Zac That explains a lot, thanks! So what confused me is the fact `console.log` is no longer a plain `console.log` in React (dev mode I guess).

以上是React是否有可能修改引用?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>