在道具中传递引用类型会使 React.memo 无用吗?

Does passing a reference type in props make React.memo useless?

假设我有一个 PersonCard 组件,它接收道具,并渲染一张显示人物信息的卡片。

interface PersonProps { 
    firstName: string,
    lastName: string
}

const PersonCard: React.FC<PersonProps> = (firstName, lastName) => {
    // render the things
}

现在,如果我要使用 React.memo 来防止不必要的重新渲染这个组件,它看起来像这样:

const PersonCard: React.FC(<PersonProps>) => 
    React.memo(({firstName, lastName}) => {
    // render the things
    }
)

根据我的理解,这会起作用:如果我的组件使用相同的 firstNamelastName 调用两次,它不会重新渲染。


现在,当我们向混合中添加引用类型时,我的问题出现了:

interface PersonProps { 
    firstName: string,
    lastName: string,
    hobbies: Array<string>
}

const PersonCard: React.FC(<PersonProps>) => 
    React.memo(({firstName, lastName, hobbies}) => {
    // render the things
    }
)

在这种情况下,React.memo 进行浅比较(作为其默认行为),并且不会对 hobbies 数组起作用。

因此,PersonCard会一直重新渲染,即使firstNamelastNamehobbies不改变,因为它会认为hobbies改变了: 这实际上与根本没有 React.memo 相同。

所以,我的问题是:我错了吗,或者在没有指定深度比较回调的情况下将任何引用类型作为 prop 传递完全使 React.memo 的观点无效?

一般来说,您是正确的:将一个或多个引用类型作为 prop 传递给已记忆的组件将阻止记忆,假设每个渲染之间的引用都发生变化(这通常是案子)。唱反调,这里有几个场景,您期望引用在每次渲染之间发生变化:

静态值

如果您将 const 的静态引用作为道具传递,您可以合理地预期它不会在渲染之间发生变化,并且应该被安全地记忆。

const IDS = [1, 2, 3];

const App = () => (
  <MyMemoizedComponent ids={IDS} />
);

这也适用于静态 let 变量,但需要注意的是,如果引用发生变化,记忆组件将重新呈现。

let IDS = [1, 2, 3];
IDS = IDS.slice(); // this would trigger a rerender

参考资料

在使用有利于引用的组件时,通常会避免使用静态值。在您不希望 .current 的值经常更改的地方使用 ref 时,您仍然可以获得记忆的好处。

const App = () => {
  const idsRef = React.useRef([1, 2, 3]);

  return (
    <MyMemoizedComponent ids={idsRef.current} />
  );
}

使用备忘录

React.memoReact.useMemo 并不相互排斥!您可以使用 useMemo 来记忆一个值,以防止它破坏记忆。

const App = () => {
  const idsMemo = React.useMemo(() => [1, 2, 3], []);

  return (
    <MyMemoizedComponent ids={idsMemo} />
  );
};

这些例子有点做作,但你可以想象这样一种场景,数据不是在本地初始化而是通过网络获取,比如

const App = ({ path }) => {
  const ids = React.useMemo(() => fetchFromNetwork(path), [path]);

  return (
    <MyMemoizedComponent ids={ids} />
  );
};

因此,总而言之,将一种或多种引用类型作为 prop 传递给已记忆的组件 通常 会破坏记忆。如果您希望引用类型频繁更改值(〜每次渲染一次),只需删除记忆,它会导致额外的工作而没有任何好处。如果您期望它经常更改,请记住它并仅在内部值更改时更新引用(或者,如您所述,为 React.memo).

为了建立在已接受的答案之上,React.memo 接受可选的第二个参数,它允许您自定义道具比较的逻辑。对于深度相等,您可以使用 lodash 中的 isEqual,或者编写您自己的深度比较函数。

import React, { memo } from "react";
import { isEqual } from "lodash";

const MyComponent = props => {
 // component logic and rendering 
};

export default memo(MyComponent, isEqual);
/**
 * Function that performs deep equality between prop instances
 * @param {P} prevProps previous instance of props
 * @param {P} nextProps next instance of props
 * @returns {boolean} true if props are equal
 */
const customIsEqual = <P>(prevProps: P, nextProps: P): boolean => {
  // deep equality check; true if props are equal
};