如何在功能组件中混合使用 useCallback 和 useRef

Posted

技术标签:

【中文标题】如何在功能组件中混合使用 useCallback 和 useRef【英文标题】:How to mix useCallback with useRef in functional components 【发布时间】:2020-06-26 00:18:14 【问题描述】:

我对 React 很陌生,并且正在开发一个无限滚动组件。多个组件将使用无限滚动并且需要一起同步(即,以编程方式滚动一个元素也会滚动其他组件)。

所以我创建了ScrollProvider,它维护组件之间的滚动状态(即使它们被重新渲染),一个较低级别的钩子useScrollSync. useScrollState 返回一个 ref 和一个 handleScroll 回调,它修改滚动提供程序中的状态.这一切都很好。但是,我想单独测量一个组件的大小。 React 团队提供的示例显示了一个回调,因为它肯定会在组件安装后执行,并且元素不会为空。问题是 div 已经有一个来自 useScrollSync 钩子的引用。

核心问题

如果除了在其上使用滚动同步之外,我还想测量我的 div,我如何将回调 ref 和其他 ref 分配给它?考虑到一个元素只能有一个 div,是否有一种模式?

一些(简化的)代码:

ScrollProvider

const ScrollContext = React.createCreateContext();

const ScrollProvider = (initialScrollTop, initialScrollLeft) => 
  const controlledElements = useRef(new Map());
  const scrollPositions = useRef(
    scrollTop: initialScrollTop, 
    scrollLeft: initialScrollLeft, 
    controllingElementKey: null
  );

  const register = (key, controlledElementRef) => 
    controlledElements.current.set(key, controlledElementRef);
  

  const handleScrollHOF = (key) =>  
    return () =>  
      scrollPositions.controllingElementKey = key;
      //some scrolling logic
    
  

  return register, scrollPositions, handleScrollHOF;


使用ScrollSync

const useScrollSync = () =>  
  const scrollContext = useContext(ScrollContext);

  const elementRef = useRef(null);
  const keyRef = useRef(key: Symbol()); // this probably could also be useState

  useEffect(() => 
    scrollContext.register(keyRef, elementRef);
  , []);

return ref: elementRef, handleScroll: handleScrollHOF(keyRef.current);

SomeComponent(第一轮)

const SomeComponent = () => 
  // this would be within the provider tree
  const ref, handleScroll = useScrollSync();

  return (
    <div onScroll=handleScroll ref=ref>some stuff</div>
  )

现在的挑战是添加测量挂钩...

使用测量

const useMeasurements = () => 
  // something like this, per the React team's Hooks FAQ
  const [measurements, setMeasurements] = useState(null);

  const measurementRef = useCallback((element) => 
    if(element !== null) 
      setMeasurements(element.getBoundingClientRect());
    
  );

  return measurementRef, measurements;

为了将它添加到 SomeComponent... SomeComponent(第 2 轮)

const SomeComponent = () => 
  // this would be within the provider tree
  const ref, handleScroll = useScrollSync();
  const measurementRef, measurements = useMeasurements();
  // I cannot assign measurementRef to this same div, 
  // and changing useMeasurements to just measure an injected ref winds up
  // with that ref being null and it never being recalculated

  return (
    <div onScroll=handleScroll ref=ref>some stuff</div>
  )

我在这里有点碰壁,或者我只是太累了。关于如何超越这一点的任何想法?

【问题讨论】:

【参考方案1】:

我看到的主要问题是您没有在 useMeasurement 中引用可行的参考。此外,useCallback 在创建 DOM 之前作为渲染的一部分同步执行。您需要在另一个 useEffect 挂钩中引用您的元素。

【讨论】:

感谢迈克尔的评论。您也可以使用函数作为参考。当元素发生变化时,它会执行 fn 并传入元素本身。 UseCallback 只是简单地记住它,因此函数引用在渲染之间是一致的(并且它本身不会导致更多的渲染)。此模式可在此处找到:reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node 啊啊啊啊!我忘记了。我没有理由使用它,它被扔进了我眼球后面的堆里。 在我看来,如果你传递了由 useScrollSync() 创建的 ref,然后在引用它时使用 useEffect(),你最终会得到可用的元素。有点俗气不得不通过裁判,但是¯\_(ツ)_/¯ 我会回来看看情况如何。祝你好运! 另一个思路:在main函数中创建ref,然后传递给每个hook?

以上是关于如何在功能组件中混合使用 useCallback 和 useRef的主要内容,如果未能解决你的问题,请参考以下文章

反应钩子 useCallback 与循环内的参数

如何在 React 中触发 useCallback

如何在自定义挂钩上使用 useCallback?

react——useMemo——useCallback——性能优化——React.memo

react——useMemo——useCallback——性能优化——React.memo

useCallback 记忆函数和useMemo 记忆组件