Nextjs渲染性能优化常见方法

·2449·6 分钟·
AI摘要: 本文详细介绍了Next.js中常见的渲染性能优化方法,包括使用useCallback缓存函数创建以避免子组件不必要的重新渲染、利用useMemo缓存复杂计算结果减少重复运算、合理配置useEffect的依赖数组控制副作用执行时机,以及正确使用Suspense实现异步组件加载时的友好等待提示。这些技术手段能有效提升React应用的性能表现。

useCallback

背景:当函数组件重新渲染的时候,会重新执行整个函数体,导致里面定义的函数会被重新创建:


function MyComponent() {

  const handleClick = () => {

    console.log("clicked");

  };



  return <button onClick={handleClick}>点我</button>;

}



  • 每次组件重新渲染,创建的handleClick都是新的函数对象

  • 重新创建函数对象一般消耗不多,没什么关系

  • 但是如果这个函数被传递给了子组件,函数的变化会导致子组件的重新渲染,消耗很大


const Child = React.memo(({ onClick }) => {

  console.log("Child render");

  return <button onClick={onClick}>子组件按钮</button>;

});



function Parent() {

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



  const handleClick = () => console.log("clicked");



  return (

    <div>

      <p>{count}</p>

      <button onClick={() => setCount(count + 1)}>加1</button>

      <Child onClick={handleClick} />

    </div>

  );

}



  • Child组件依赖onClick函数, handleClick的重新创建,导致了Child组件的重新渲染

  • 解决办法:缓存函数创建


const handleClick = useCallback(() => {

  console.log("clicked");

}, []);



  • 只有当[]中的内容发生变化时候,才会重新生成新的函数

useMemo

上面讲到,函数重新创建一般消耗不大,但是如果函数计算逻辑复杂,函数计算也是可能消耗很大的, 那么可以用useMemo来缓存函数计算结果


const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

  • useMemo缓存一个计算结果

  • 只有依赖项[a, b]发生变化时,才会重新计算

  • 避免了复杂逻辑重新计算

useEffect

函数组件渲染的时候,不仅仅渲染JSX,还需要进行一些其他操作(数据获取等)

  • useEffect(() =>{...}): 每次渲染结束都执行

  • useEffect(() =>{...}, []):只有第一次渲染结束才执行

  • useEffect(() =>{...}, [a, b]): 只要a或者b发生变化,渲染结束都执行


useEffect(() => {

  const handleResize = () => console.log(window.innerWidth);

  window.addEventListener("resize", handleResize);



  return () => window.removeEventListener("resize", handleResize);

}, []); // 空数组 → 组件挂载时注册,卸载时清理



  • 函数体是渲染结束之后做的事情

  • 渲染出来的组件如果被卸载也要做一些事情,可以通过return封装

Suspense

用来异步渲染:


// app/page.tsx

import ResourceList from './ResourceList';



export default function Page() {

  return (

    <Suspense fallback={<div>Loading resources...</div>}>

      <ResourceList />

    </Suspense>

  );

}



// app/ResourceList.tsx

async function ResourceList() {

  const resources = await fetch('https://api.example.com/resources').then(res => res.json());

  return (

    <ul>

      {resources.map(r => <li key={r.id}>{r.title}</li>)}

    </ul>

  );

}



  • 只有被包裹的组件是异步组件才有用

  • <Suspense>内部直接放<div>没啥用(之前一直都用错了)