useMemo behaves different for inline component vs. normal component

Jür*_*ter 5 reactjs

I have the following React code

const { useState, useMemo, Fragment } = React;

function Rand() {
    return <Fragment>{Math.random()}</Fragment>;
}

const App = () => {
    const [show, setShow] = useState(true);
    
    // The inline component gets memoized. But <Rand /> does not
    const working = useMemo(() => <Fragment>{Math.random()}</Fragment>, []);
    // The rand component is not memoized and gets rerendred
    const notWorking = useMemo(() => <Rand />, []);
  
    return(
        <Fragment>
            <button
                onClick={() => {
                  setShow(!show);
                }}>
                {show?"Hide":"Show"}
            </button>
            <br />
            Working: 
            {show && working}
            <br />
            Not Working: 
            {show && notWorking}
        </Fragment>
    );
}

ReactDOM.render(
    <App />,
    document.getElementById("root")
);
Run Code Online (Sandbox Code Playgroud)
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Run Code Online (Sandbox Code Playgroud)

It uses useMemo 2 times.

The first time it uses an inline component to "initialize" and memoize a component ( const working = useMemo(() => <>{Math.random()}</>, []);)

The second time it uses a component which was made outside the app component (const notWorking = useMemo(() => <Rand />, []);)

Both components used in the useMemo function have the exact same code, which is <>{Math.random()}</>.

意想不到的部分来了,当我隐藏(单击按钮)并再次显示两个记忆组件时,它们的行为有所不同。第一个将始终显示与第一次初始化时获得的相同的随机数。而秒一会重新初始化每次。

第一次渲染

在此处输入图片说明

第二次渲染(隐藏)

在此处输入图片说明

第三次渲染(再次显示)

在此处输入图片说明

从截图中可以看出,第一个组件的随机数保持不变,而第二个则没有。

我的问题:

  • 在这两种情况下,如何防止重新渲染/重新初始化组件?
  • 为什么它目前的行为如此?

有趣的是,如果我使用计数器而不是显示/隐藏,它确实会被记住:

const { useState, useMemo, Fragment } = React;

function Rand() {
    return <Fragment>{Math.random()}</Fragment>;
}

const App = () => {
    const [counter, setCounter] = useState(0);
    
    // The inline component gets memoized. But <Rand /> does not
    const working = useMemo(() => <Fragment>{Math.random()}</Fragment>, []);
    // The rand component is not memoized and gets rerendred
    const notWorking = useMemo(() => <Rand />, []);
  
    return(
        <Fragment>
            <button
                onClick={() => {
                  setCounter(c => c + 1);
                }}>
                Update ({counter})
            </button>
            <br />
            Working: 
            {working}
            <br />
            Not Working: 
            {notWorking}
            <br />
            <code>Rand</code> used directly:
            <Rand />
        </Fragment>
    );
}

ReactDOM.render(
    <App />,
    document.getElementById("root")
);
Run Code Online (Sandbox Code Playgroud)
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Run Code Online (Sandbox Code Playgroud)

这是一个自己尝试的代码笔https://codepen.io/brandiatmuhkuh/pen/eYWmyWz

T.J*_*der 2

\n

为什么它现在的表现是这样的?

\n
\n

<Rand />不调用您的组件函数。它只是调用React.createElement创建 React 元素(而不是它的实例)。您的组件函数用于在您使用它时呈现元素实例。在您的“工作”示例中,您正在执行以下操作:

\n
<>{Math.random()}</>\n
Run Code Online (Sandbox Code Playgroud)\n

...它在Math.random片段中调用并使用其结果作为文本(而不是组件)。

\n

但你的“不工作”的例子就是这样:

\n
<Rand />\n
Run Code Online (Sandbox Code Playgroud)\n

该元素已创建,但未使用,并且您的函数未调用。“你的函数没有被调用”部分可能会令人惊讶\xc2\xa0\xe2\x80\x94,当我开始使用 React\xc2\xa0\xe2\x80\x94 时,这对我来说是令人惊讶的,但这是真的:

\n

\r\n

\n

\n

在这两种情况下如何防止重新渲染/重新初始化组件?

\n
\n

如果您执行示例中所做的操作,即将挂载的组件完全从树中取出,那么您将卸载该组件实例;当你把它放回去时,你的函数被再次调用,所以你将得到一个新值。(这也是带有计数器的版本没有表现出此行为的原因:组件实例保持安装状态。)

\n

如果你想记住它显示的内容,一种方法是将其作为道具传递给它,并记住你传递给它的内容:

\n

\r\n
\r\n
const { Fragment } = React;\n\nfunction Rand() {\n    console.log("Rand called");\n    return <Fragment>{Math.random()}</Fragment>;\n}\n\nconsole.log("before");\nconst element = <Rand />;\nconsole.log("after");\n\n// Wait a moment before *using* it\nsetTimeout(() => {\n    ReactDOM.render(\n        element,\n        document.getElementById("root")\n    );\n}, 1000);
Run Code Online (Sandbox Code Playgroud)\r\n
<div id="root"></div>\n\n<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>\n<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n