Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
122 views
in Technique[技术] by (71.8m points)

javascript - Is it possible that React modifies a reference?

I made a small experiment with implementing an observer pattern manually in React (*). It basically works, but with a highly unexpected detail. Consider this minimal example:

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>;
}

CodeSandbox

In the console log this produces:

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

As you can see, the number of registered callbacks has suddenly changed to 2, even though only 1 callback has been registered. How is this possible? Do I have a blind spot or is this somehow an implication of how React works?


(*) I know that this problem can be solved by a combination of useImperativeHandle and forwardRef. The above is just an experiment to investigate alternatives, and I'm asking for learning purposes.

question from:https://stackoverflow.com/questions/65925160/is-it-possible-that-react-modifies-a-reference

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Because you put the register logic in render function (function component's body), it will register it on every component's render.

And since you have StrictMode wrapper, it invoked twice:

Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:

  • ...
  • Function component bodies

You can either remove the StrictMode (not recommended) or write the logic in useEffect as I guess you want it registered on observer change:

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

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

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

Edit patient-cache-emkoo

Note that in StrictMode the logs are silenced, so you don't see the second console.log("registering observer");

Starting with React 17, React automatically modifies the console methods like console.log() to silence the logs in the second call to lifecycle functions.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...