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 的工作方式?
(*)我知道,这个问题可以通过一个被解决的组合useImperativeHandle和forwardRef。以上只是一个研究替代方案的实验,我要求的是学习目的。
回答
因为您将注册逻辑放在 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).