在道具中传递引用类型会使 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
}
)
根据我的理解,这会起作用:如果我的组件使用相同的 firstName
和 lastName
调用两次,它不会重新渲染。
现在,当我们向混合中添加引用类型时,我的问题出现了:
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
会一直重新渲染,即使firstName
、lastName
和hobbies
不改变,因为它会认为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.memo
和 React.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
};
假设我有一个 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
}
)
根据我的理解,这会起作用:如果我的组件使用相同的 firstName
和 lastName
调用两次,它不会重新渲染。
现在,当我们向混合中添加引用类型时,我的问题出现了:
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
会一直重新渲染,即使firstName
、lastName
和hobbies
不改变,因为它会认为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.memo
和 React.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
};