如何在渲染之前使用React钩子来响应道具更改

时间:2019-08-30 16:39:49

标签: javascript reactjs react-hooks

我正在尝试使用功能组件和React钩子来实现简化的自动滚动器,当子内容溢出时,该自动滚动器将容器自动滚动到底部。但是,只有在滚动条已经位于底部时,才应进行自动滚动(例如,如果用户向上滚动以查看输出,则当出现新内容时滚动位置不应更改)。

我知道如何通过使用引用并在clientHeight,scrollTop和scrollHeight上执行计算来实现自动滚动行为。

我遇到的问题是,在重新渲染组件之前,我需要计算shouldAutoScroll()检查。

我的流程需要如下所示:

<container>
   {props.children}
</container>

When props.children changes:
   1. Check if the scrollbar is near the bottom and store the result
   2. Update container to reflect the new props.children
   3. If the check from step 1 is true, scroll to the bottom

我似乎找不到使用useEffect和/或useLayoutEffec的方法。使用这些时,会发生以下情况:

   1. Scroll position is at bottom
   2. props.children updates with new items
   3. <container> is rerendered, pushing the scrollbar up
   4. The checkScrollBarBottom() method is called and returns false
   5. The scrollbar is not auto-scrolled

我需要保持组件的通用性,以便无论组件或元素props.children是什么类型,它都可以自动滚动。在某些情况下,对props.chldren的更改可能只是一行。在其他情况下,可能是20行,也可能是图像。

如果我使用的是老式样式的类组件,则可以在 componentWillReceiveProps()中进行计算。如何用钩子复制它?

1 个答案:

答案 0 :(得分:0)

我发现了一个可行的解决方案,但似乎有些混乱。

解决方案是在容器的shouldAutoScroll()事件期间计算和更新onScroll()。这似乎很混乱,因为我正在捕获大量无用的中间滚动信息,而我所关心的只是更新开始时(但组件重新呈现之前)的滚动位置。

完整代码:

import React, { useRef, useEffect, useLayoutEffect } from 'react';
import styles from './AutoScroller.module.scss';

export function AutoScroller({children, className=''}) {
  const classNames = `${styles.default} ${className}`;

  const containerRef = useRef(null);
  const shouldAutoScroll = useRef(false);

  function updateShouldAutoScroll(element, tolerance) {
    const {scrollHeight, scrollTop, clientHeight} = element;
    const result = scrollHeight - scrollTop <= clientHeight + tolerance;

    shouldAutoScroll.current = result;
  }

  function onContainerScroll(e) {
    updateShouldAutoScroll(e.target, 25)
  }

  useEffect(function autoScroll() {
    if (shouldAutoScroll.current) {
      const element = containerRef.current;
      element.scrollTop = element.scrollHeight;
    }
  });

  return (
    <div className={classNames} ref={containerRef} onScroll={onContainerScroll}>
      {children}
    </div>
  )
}