繁体   English   中英

私有组件的 React.memo 与 useMemo

[英]React.memo vs useMemo for private components

想象一下,您有一个组件需要一个函数,该函数在该组件的层次结构中呈现特定的子树:

const Timeline = ({ renderDetails, ... }: { renderDetails: (rowData, sectionID, rowID) => React.Node }) => <div>{renderChildren(...)}</div>;

使用有没有区别:

const MyComponent = React.memo(({ someProp }) => <someComponentHierarchyHere>...</someComponentHierarchyHere>);

const ParentComponent = ({ someProp }) => {
    const renderChildren = useCallback(() => <MyComponent someProp={someProp} />, [someProp]);

    return <Timeline renderChildren={renderChildren} />;
};

对比

const ParentComponent = ({ someProp }) => {
    const MyComponent = useMemo(({ someProp }) => <someComponentHierarchyHere>...</someComponentHierarchyHere>);
    const renderChildren = useCallback((rowData, sectionID, rowID) => <MyComponent someProp={someProp} />, [someProp]);

    return <Timeline renderChildren={renderChildren} />;
};

或者简单地说:

const ParentComponent = ({ someProp }) => {
    const renderChildren = useCallback((rowData, sectionID, rowID) => <someComponentHierarchyHere>...</someComponentHierarchyHere>, [someProp]);

    return <Timeline renderChildren={renderChildren} />;
};

? 我对 React 渲染器将采取的决策以及MyComponent组件(或它在组件树中插入的层次结构)是否会被记忆特别感兴趣。

通常,组件定义应该始终是静态的,因此React.memo是这里的正确选择。

如果您的内部组件是完全无状态的,它似乎可以工作,但是您将无法在内部组件内部使用任何挂钩,因此对于任何复杂的事情,无论如何您都必须以其他方式进行。

这样做的原因是,在内部,React 会跟踪在任何给定时间正在渲染的组件,这就是它如何确定useState和其他钩子返回的值的方式。 请注意,您没有在useMemo中使用依赖项数组,这意味着它将在每次渲染时重新创建,因此它实际上毫无意义(react hooks linting 规则会警告您这一点。但是,即使使用一个空的依赖项数组,它在其中使用钩子仍然不安全。一个常见的误解是,没有依赖关系的useMemo会创建一个常量,但事实并非如此。这是一种通常会阻止重新计算值的优化,但不能保证。在内部, React 可以随时选择丢弃 useMemo 的useMemo结果,如果发生这种情况,它会随机丢弃你的状态,因此 React 完全禁止它。

import { useMemo, useState } from 'react';

export default function App() {
  const MyComponent = useMemo(() => {
    // This line errors:
    // React Hook "useState" cannot be called inside a callback.
    // React Hooks must be called in a React function component or a
    // custom React Hook function. (react-hooks/rules-of-hooks) eslint
    const [input, setInput] = useState('')
  
    return (
      <input value={input} onChange={(e) => setInput(e.target.text)} />
    )
  })

  return <MyComponent />;
}

两者具有相似的名称并且是优化方法。 但这就是相似性结束的地方,它们是完全不同的 React 特性。

React.memo适用于当您发现现有组件的渲染成本很高,并且您无法在内部对其进行优化时。 把它包装起来,让 React 做一个额外的检查。

React.useMemo用于通过保存昂贵函数调用的返回值来内部优化组件。

执行

useMemo

源代码相对简单。 如果 deps 没有改变,则返回之前的值。

const prevDeps: Array<mixed> | null = prevState[1];
if (prevState !== null) {
    if (areHookInputsEqual(nextDeps, prevDeps)) {
        return prevState[0];
    }
}

const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;

在所有其他情况下,它调用(据说很昂贵) nextCreate函数。 结果首先保存在挂钩日志中(用于下一次渲染),然后返回。

对于初始(“mount”)渲染,React 使用了一个更简单的实现,它只调用nextCreate ,保存它,然后返回它。

React.memo

React.memo是一种完全不同类型的函数。 它是一种高阶组件,但有一个问题。 它不会返回一个常规的 React 组件,而是一个特殊的REACT_MEMO_TYPE 这种类型最终会导致在执行渲染工作时对这些元素进行特殊处理。

这有更多的移动部分,但为了简单起见,我只是复制了 props check

let compare = Component.compare; // This is what you pass into 2nd argument.
compare = compare !== null ? compare : shallowEqual;

if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
  return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}

useMemo不同,它不涉及存储任何额外的状态。 相反,比较函数用于避免调用(据说很昂贵)渲染函数。 它只是在开始之前中止一大块工作,当它发现它没有用时。

结论

这些实际上是两个具有相似名称和目的的不同事物。

适用于 OP 用例

你可能需要React.memo

当然,您总是首先衡量是否有任何需要优化的地方。 这些事情中的每一个都有其自身的成本。 你不能把它洒在周围并自动获得性能。 如果没有通过避免昂贵的任务来平衡,它只会使代码变得更慢和更复杂。

以下没有做任何有用的事情:

useMemo(({ someProp }) => <someComponentHierarchy>...</someComponentHierarchy>)

如果你检查上面的 React 源代码,你会看到nextCreate函数被调用时不带参数。

无论您在其中放入什么, useMemo都不会像React.memo那样对 Memoized 元素类型进行相同的检查,因此它不能用于相同的目的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM