React - JSX 元素的 useCallback 与 useMemo

React - useCallback vs useMemo for JSX elements

我已经实现了这个组件:

function CardList({
  data = [],
  isLoading = false,
  ListHeaderComponent,
  ListEmptyComponent,
  ...props
}) {
  const keyExtractor = useCallback(({ id }) => id, []);

  const renderItem = useCallback(
    ({ item, index }) => (
      <Card
        data={item}
        onLayout={(event) => {
          itemHeights.current[index] = event.nativeEvent.layout.height;
        }}
      />
    ),
    []
  );

  const renderFooter = useCallback(() => {
    if (!isLoading) return null;

    return (
      <View style={globalStyles.listFooter}>
        <Loading />
      </View>
    );
  }, [isLoading]);

  return (
    <FlatList
      {...props}
      data={data}
      keyExtractor={keyExtractor}
      renderItem={renderItem}
      ListHeaderComponent={ListHeaderComponent}
      ListFooterComponent={renderFooter()}
      ListEmptyComponent={ListEmptyComponent}
    />
  );
}

由于我的 CardList 组件很重,我尝试按照这些 tips.

对其进行优化

但是,我认为我应该使用 useMemo 而不是对 renderFooter 使用 useCallback,以便记住生成的 JSX 而不是方法:

const ListFooterComponent = useMemo(() => {
  if (!isLoading) return null;

  return (
    <View style={globalStyles.listFooter}>
      <Loading />
    </View>
  );
}, [isLoading]);

我说得对吗?

“应该”是见仁见智的事情,但你当然可以。 React 元素是完全可重用的。但是,它不太可能对您的组件产生任何真正的影响,因为创建元素很快并且 renderFooter 只是立即用于已经 运行 的组件(不像 keyExtractorrenderItem,您要将其传递给 FlatList,因此您希望尽可能使它们稳定,以便 FlatList 可以优化其 re-rendering)。但你当然可以做到。

useMemo 在这里非常明智,因为它会记住结果(JSX)。正如您所说,useCallback 记住了 函数 而不是结果。

如果您想避免昂贵的计算,请使用useMemouseCallback 用于记忆 callback/function.

Official 文档确实有一个将 useMemo 与 JSX 一起使用以避免重新渲染的示例:

function Parent({ a, b }) {
  // Only re-rendered if `a` changes:
  const child1 = useMemo(() => <Child1 a={a} />, [a]);
  // Only re-rendered if `b` changes:
  const child2 = useMemo(() => <Child2 b={b} />, [b]);
  return (
    <>
      {child1}
      {child2}
    </>
  )
}

个人观察:

但更详细地说,似乎并不是 useMemo 这里神奇地阻止了重新渲染,而是你在组件层次结构中的同一位置渲染 相同的引用, 使 react 跳过重新渲染。

这里:

let Child = (props) => {
  console.log('rendered', props.name);

  return <div>Child</div>;
};

export default function Parent() {
  let [a, setA] = React.useState(0);
  let [b, setB] = React.useState(0);

  // Only re-rendered if `a` changes:
  const child1 = React.useMemo(() => <Child a={a} name="a" />, [a]);
  // Only re-rendered if `b` changes:
  const child2 = React.useMemo(() => <Child b={b} name="b" />, [b]);

  return (
    <div
      onClick={() => {
        setA(a + 1);
      }}
    >
      {a % 2 == 0 ? child2 : child1}
    </div>
  );
}

如果您一直单击 div,您会看到它仍然打印“rendered b”,即使我们从未更改 b 属性。这是因为 React 每次在 div 中都会收到一个 不同的引用 ,并且不会优化重新渲染 - 就像您只渲染 [=19] 一样=] 没有那个条件和 child1.

注意:当 React 在同一位置接收到相同的元素引用时会跳过渲染的事实是 known,因此显然 useMemo 技术之所以有效,是因为它出现时渲染优化。