React lazy & memo && useMemo && useCallback

261 阅读2分钟

lazy

lazy 能够让你在组件第一次被渲染之前延迟加载组件的代码。

注:不要在其他组件内部声明lazy组件(将导致在重新渲染时重置所有状态):

import { lazy } from 'react';

function Editor() {

  // 🔴 Bad: 这将导致在重新渲染时重置所有状态

  const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));
  // ...
}

相反,总是在模块的顶层声明它们:

import { lazy } from 'react';

// ✅ Good: 将 lazy 组件声明在组件外部

const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));

function Editor() {
  // ...
}
import { lazy } from 'react';  

const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));
// 用<Suspense>标签包裹,在加载过程中可以加载 loading 组件
<Suspense fallback={<Loading />}>  
    <MarkdownPreview />  
</Suspense>

MarkdownPreview 的代码只有在你尝试渲染它时才会被加载。如果 MarkdownPreview 还没有加载完成,将显示 Loading

import { useState, Suspense, lazy } from 'react';
import Loading from './Loading.js';

const MarkdownPreview = lazy(() => delayForDemo(import('./MarkdownPreview.js')));

export default function MarkdownEditor() {
  const [showPreview, setShowPreview] = useState(false);
  const [markdown, setMarkdown] = useState('Hello, **world**!');
  return (
    <>
      <textarea value={markdown} onChange={e => setMarkdown(e.target.value)} />
      <label>
        <input type="checkbox" checked={showPreview} onChange={e => setShowPreview(e.target.checked)} />
        Show preview
      </label>
      <hr />
      //此处进行加载
      {showPreview && (
        <Suspense fallback={<Loading />}>
          <h2>Preview</h2>
          <MarkdownPreview markdown={markdown} />
        </Suspense>
      )}
    </>
  );
}

// 添加一个固定的延迟时间,以便你可以看到加载状态
function delayForDemo(promise) {
  return new Promise(resolve => {
    setTimeout(resolve, 2000);
  }).then(() => promise);
}

memo

(记忆化只与从父组件传递给组件的props有关。)

memo 允许你的组件在 props 没有改变的情况下跳过重新渲染 例:

import { memo, useState } from 'react';

export default function MyApp() {
  const [name, setName] = useState('');
  const [address, setAddress] = useState('');
  return (
    <>
      <label>
        Name{': '}
        <input value={name} onChange={e => setName(e.target.value)} />
      </label>
      <label>
        Address{': '}
        <input value={address} onChange={e => setAddress(e.target.value)} />
      </label>
      <Greeting name={name} />
    </>
  );
}

①memo包裹前的组件

此处没有包裹 memo 所以不管是 name 变化还是 address 变化 都会导致 Greeting 组件重新渲染

function Greeting({ name }) {
  console.log("Greeting was rendered at", new Date().toLocaleTimeString());
  return <h3>Hello{name && ', '}{name}!</h3>;
};

②memo包裹后的组件 作为对比

Greeting组件在name 更改时重新渲染(因为那是它的 props 之一),

但是在address更改时不会重新渲染(因为它不作为 props 传递给Greeting)

const Greeting = memo(function Greeting({ name }) {
  console.log("Greeting was rendered at", new Date().toLocaleTimeString());
  return <h3>Hello{name && ', '}{name}!</h3>;
});

如果传递给组件的 props 始终不同,例如在渲染期间传递对象普通函数,则 memo 是完全无用的。这就是为什么你通常需要在 memo 中同时使用 useMemouseCallback

1.即使一个组件被(memo)记忆化了,当它自身的(组件内部定义的 state)状态发生变化时,它仍然会重新渲染。memoization 与从父组件传递给组件的 props 有关。

2.即使组件已被记忆化,当其使用的 context 发生变化时,它仍将重新渲染。记忆化只与从父组件传递给组件的 props 有关。

memo的第二个参数用于自己定义比较参数的方法(react的比较是浅层的)

const Chart = memo(function Chart({ dataPoints }) {  
// ...  
}, arePropsEqual);  

function arePropsEqual(oldProps, newProps) {  
  return ( 
    oldProps.dataPoints.length === newProps.dataPoints.length &&  

    oldProps.dataPoints.every((oldPoint, index) => {  

    const newPoint = newProps.dataPoints[index];  

    return oldPoint.x === newPoint.x && oldPoint.y === newPoint.y;  
})  
);  
}

useMemo

useMemo 是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果

useCallback

useCallback 是一个允许你在多次渲染中缓存函数的 React Hook