在 nextjs 中使用 memo 避免不必要的组件渲染

Avoid unnecessary component rendering with memo in nextjs

我正在尝试通过 nextjs 了解 React 的行为。 我有一个 index.js 页面,其中一个组件 Homecard 显示了三次,一个按钮增加了一个值。

每次单击按钮时,所有 Homecard 组件都会重新渲染。

index.js

import { Homecard } from '../components/Homecard'
import { useState } from 'react'

export default function Home() {

    const [count, increment] = useState(0);

    const homecards = [
        {
            "main": "main0",
            "sub": "sub0",
            "desc": "Desc0",
            "nav": [{
                "href": "/page0",
                "value": "see"
            }]
        },
        {
            "main": "main1",
            "sub": "sub1",
            "desc": "Desc1",
            "nav": [{
                "href": "/page1",
                "value": "see"
            }]
        },
        {
            "main": "main2",
            "sub": "sub2",
            "desc": "Desc2",
            "nav": [{
                "href": "/page2",
                "value": "see"
            }]
        }
    ];

    const handleCount = () => {
        increment(count => count + 1);
    }

    return (
        <>
            <div className='d-flex justify-content-between' style={{ marginLeft: '-1.5rem' }}>
                {
                    homecards.map((homecard, index) => (
                        <Homecard
                            key={index}
                            main={homecard.main}
                            sub={homecard.sub}
                            desc={homecard.desc}
                            nav={homecard.nav}
                        />
                    ))
                }
            </div>
            <button onClick={handleCount}>increment {count}</button>
        </>
    )
}

homecard.js

export default function Homecard({ main, sub, desc, nav }) {
    console.log('render Homecard');
    return (
        <div className={`${styles.homecard}`}>
            <div>
                <h3>
                    {main}
                {sub &&
                    <span>{sub}</span>
                }
                </h3>
                <p>{desc}</p>
                {nav &&
                    <ul>
                        {nav.map((link, index) => (
                            <li key={index}>
                                <Link href={link.href}>
                                    <a>{link.value}</a>
                                </Link>
                            </li>
                        ))}
                    </ul>
                }
            </div>

        </div>
    )
}

我试着用 React.memo 包裹我的 Homecard

const Homecard = React.memo(({ main, sub, desc, nav }) => {})

但是当我点击我的按钮时,我仍然看到 console.log('render Homecard');。 我怎样才能只更新我的按钮而不更新其他组件?

问题是您在 Home 的每个渲染器上 重新创建 您的 homecards 数组,因此每个 nav 对象都是一个new 对象,所以 React.memo 看到了 props 的不同,并没有优化掉随后的 re-renders.

有几种方法可以修复它:

  1. Home外定义homecards;它是不变的,因此没有理由在每次调用 Home 时重新创建它。

  2. 如果由于某种原因你不能这样做,你可以将第二个参数传递给 React.memo,“areEqual”函数,它将接收旧的道具和新的;在函数中,进行深入比较,看看是否 nav 个对象具有相同的 属性 值,即使它们是不同的对象。

#1 示例:

const { useState } = React;

/*export default*/ const Homecard = React.memo(({ img, main, sub, desc, nav }) => {
    console.log('render Homecard');
    return (
        <div className={`${""/*styles.homecard*/}`}>
            <div>
                <h3>
                    {main}
                {sub &&
                    <span>{sub}</span>
                }
                </h3>
                <p>{desc}</p>
                {nav &&
                    <ul>
                        {nav.map((link, index) => (
                            <li key={index}>
                                <a href={link.href}>
                                    {link.value}
                                </a>
                            </li>
                        ))}
                    </ul>
                }
            </div>

        </div>
    )
});

const homecards = [
    {
        "main": "main0",
        "sub": "sub0",
        "desc": "Desc0",
        "nav": [{
            "href": "/page0",
            "value": "see"
        }]
    },
    {
        "main": "main1",
        "sub": "sub1",
        "desc": "Desc1",
        "nav": [{
            "href": "/page1",
            "value": "see"
        }]
    },
    {
        "main": "main2",
        "sub": "sub2",
        "desc": "Desc2",
        "nav": [{
            "href": "/page2",
            "value": "see"
        }]
    }
];

/*export default*/ function Home() {

    const [count, increment] = useState(0);

    const handleCount = () => {
        increment(count => count + 1);
    }

    return (
        // Sadly, Stack Snippets don't support <>...</>
        <React.Fragment>
            <div className='d-flex justify-content-between' style={{ marginLeft: '-1.5rem' }}>
                {
                    homecards.map((homecard, index) => (
                        <Homecard
                            key={index}
                            main={homecard.main}
                            sub={homecard.sub}
                            desc={homecard.desc}
                            nav={homecard.nav}
                        />
                    ))
                }
            </div>
            <button onClick={handleCount}>increment {count}</button>
        </React.Fragment>
    )
}


const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Home />);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

(警告:为了在 Stack Snippets 中完成这项工作,我做了一些小改动,比如替换 Link 并使用 React.Fragment 而不是 <>...</>,所以不要将其直接复制并粘贴回您的项目,只需将 homecards 移动到您现有的代码中即可。)