【React】memo、useMemo与useCallback的区别

306 阅读3分钟

前面了解了 useMemouseCallback 的基本使用,最近了解到 React 中还有一个 memo 的函数,这里做一下对比。

memo

memo 是 React 16.6 发布的一个高阶组件,允许组件在 props 没有改变的情况下跳过重新渲染。

语法

memo(Component, arePropsEqual?)

使用 memo 将组件包装起来,以获得该组件的一个 记忆化 版本。通常情况下,只要该组件的 props 没有改变,这个记忆化版本就不会在其父组件重新渲染时重新渲染。

基本使用

const Greeting = memo((props: { name: string }) => {
  console.log("greeting render");
  return <div>{`hello  ${props.name}`}</div>;
});

使用场景

import React, { memo, useState } from "react";
type IProps = {
  name: string;
};
// 改变count会引起 greeting组件的render
const Greeting = (props: { name: string }) => {
  console.log("greeting render");
  return <div>{`hello  ${props.name}`}</div>;
};
const HelloFunction: React.FC<IProps> = (props) => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("hello");
  const changeName = () => {
    setName("world");
  }
  const changeCount = () => {
    setCount(count + 1);
  }
  return (
    <>
      <Greeting name={name} />
      <button onClick={changeName}>change name</button>
      <button onClick={changeCount}>change count</button>
      <div>value: {count}</div>
    </>
  );
}
export default HelloFunction;

使用 memo 包裹组件,当 props 没有状态改变时 Greeting 组件不会重新渲染。

import React, { memo, useState } from "react";
type IProps = {
  name: string;
};
// =================================== //
// 改变count不会引起 greeting组件的render了
const Greeting = memo((props: { name: string }) => {
  console.log("greeting render");
  return <div>{`hello  ${props.name}`}</div>;
});
// =================================== //
const HelloFunction: React.FC<IProps> = (props) => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("hello");
  const changeName = () => {
    setName("world");
  }
  const changeCount = () => {
    setCount(count + 1);
  }
  return (
    <>
      <Greeting name={name} />
      <button onClick={changeName}>change name</button>
      <button onClick={changeCount}>change count</button>
      <div>value: {count}</div>
    </>
  );
}
export default HelloFunction;

总结

  • 使用 memo 包裹组件,当组件的 propsstatecontext 没有改变时组件不会被重新渲染
  • 不执行渲染更新时不会执行子组件代码
  • 作用对象为组件并返回一个新组件
  • memo 是一个 高阶组件

useMemo

使用场景

import React, { memo, useEffect, useMemo, useState } from "react";
type IProps = {
  name: string;
};
// 父组件count发生state改变,子组件也会重新渲染
const Greeting = (props: { names: string[] }) => {
  useEffect(() => {
    console.log("greeting useEffect");
  }, [props.names]);
  return <>
    <div>
      {
        props.names.map((item, index) => {
          return <div key={index}>{item}</div>;
        })
      }
    </div>
  </>;
};
const HelloFunction: React.FC<IProps> = (props) => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("hello");
  const nameList = () => {
    console.log("nameList render");
    let names = [];
    for (let i = 0; i < 10; i++) {
      names.push(`${i}`);
    }
    return names;
  };
  const changeName = () => {
    setName("world");
  }
  const changeCount = () => {
    setCount(count + 1);
  }
  return (
    <>
      <Greeting names={nameList()} />
      <button onClick={changeName}>change name</button>
      <button onClick={changeCount}>change count</button>
      <div>value: {count}</div>
    </>
  );
}
export default HelloFunction;

useMemo 会缓存计算结果,当依赖项没有改变时不会重新执行计算函数。

import React, { memo, useEffect, useMemo, useState } from "react";
type IProps = {
  name: string;
};
// 父组件count发生state改变,子组件也会重新渲染
const Greeting = (props: { names: string[] }) => {
  useEffect(() => {
    console.log("greeting useEffect");
  }, [props.names]);
  return <>
    <div>
      {
        props.names.map((item, index) => {
          return <div key={index}>{item}</div>;
        })
      }
    </div>
  </>;
};
const HelloFunction: React.FC<IProps> = (props) => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("hello");
  
  // =================================== //
  const nameList = useMemo(() => {
    console.log("nameList render");
    let names = [];
    for (let i = 0; i < 10; i++) {
      names.push(`${i}`);
    }
    return names;
  }, [name]);
  // =================================== //
  const changeName = () => {
    setName("world");
  }
  const changeCount = () => {
    setCount(count + 1);
  }
  return (
    <>
      <Greeting names={nameList} />
      <button onClick={changeName}>change name</button>
      <button onClick={changeCount}>change count</button>
      <div>value: {count}</div>
    </>
  );
}
export default HelloFunction;

总结

  • 使用 useMemo 包裹计算函数需要返回一个计算结果并会缓存当前函数执行结果,当依赖项没有发生改变时不会执行结算函数
  • 不执行渲染更新时会执行子组件代码不会执行 renturn代码
  • 作用对象为带返回值的计算函数,需要设置依赖项
  • useMemo 是一个 React Hook

useCallback

useCallbackuseMemo****缓存函数 的一种特殊情况。

使用场景

import React, { memo, useEffect, useMemo, useState } from "react";
type IProps = {
  name: string;
};
// 父组件count发生state改变,子组件也会重新渲染
const Greeting = (props: { func: () => void }) => {
  useEffect(() => {
    console.log("greeting useEffect");
  }, [props.func]);
  return <>
    <button onClick={props.func}>change name</button>
  </>;
};
const HelloFunction: React.FC<IProps> = (props) => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("hello");
  const handle = () => {
    console.log("handle");
  }
  const changeName = () => {
    setName("world");
  }
  const changeCount = () => {
    setCount(count + 1);
  }
  return (
    <>
      <Greeting func={handle} />
      <button onClick={changeName}>change name</button>
      <button onClick={changeCount}>change count</button>
      <div>value: {count}</div>
    </>
  );
}
export default HelloFunction;

使用 useCallback 会缓存函数,当依赖项没有发生改变时不会触发子组件渲染

import React, { memo, useCallback, useEffect, useMemo, useState } from "react";
type IProps = {
  name: string;
};
// 父组件count发生state改变,子组件也会重新渲染
const Greeting = (props: { func: () => void }) => {
  useEffect(() => {
    console.log("greeting useEffect");
  }, [props.func]);
  return <>
    <button onClick={props.func}>change name</button>
  </>;
};
const HelloFunction: React.FC<IProps> = (props) => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("hello");
  
  // =================================== //
  const handle = useCallback(() => {
    console.log("handle");
  }, [name])
  // =================================== //
  
  const changeName = () => {
    setName("world");
  }
  const changeCount = () => {
    setCount(count + 1);
  }
  return (
    <>
      <Greeting func={handle} />
      <button onClick={changeName}>change name</button>
      <button onClick={changeCount}>change count</button>
      <div>value: {count}</div>
    </>
  );
}
export default HelloFunction;

总结

  • 使用 useCallback 包裹函数时会缓存当前函数没有返回值,当依赖项没有发生改变时不会执行结算函数。
  • 不执行渲染更新时会执行子组件代码不会执行 renturn代码
  • 作用对象为函数,需要设置依赖项

友情提示

见原文:【React】memo、useMemo与useCallback的区别)

本文同步自微信公众号 "程序员小溪" ,这里只是同步,想看及时消息请移步我的公众号,不定时更新我的学习经验。