Repository: alibaba/hooks
Pull request #1026 (2021-07-12 07:15:50)

refactor: useScroll
fixed: #767 


Patch:
diff --git a/packages/hooks/src/useScroll/demo/demo1.tsx b/packages/hooks/src/useScroll/demo/demo1.tsx
index 2f5632f4..d0fa08bd 100644
--- a/packages/hooks/src/useScroll/demo/demo1.tsx
+++ b/packages/hooks/src/useScroll/demo/demo1.tsx
@@ -14,7 +14,7 @@ export default () => {
   const scroll = useScroll(ref);
   return (
     <>
-      <div>{JSON.stringify(scroll)}</div>
+      <p>{JSON.stringify(scroll)}</p>
       <div
         style={{
           height: '160px',
diff --git a/packages/hooks/src/useScroll/demo/demo2.tsx b/packages/hooks/src/useScroll/demo/demo2.tsx
index 1d6775e4..605913b1 100644
--- a/packages/hooks/src/useScroll/demo/demo2.tsx
+++ b/packages/hooks/src/useScroll/demo/demo2.tsx
@@ -1,5 +1,5 @@
 /**
- * title: Detect Whole Page Scroll
+ * title: Listen Page Scroll
  * desc: Try to scroll this webpage.
  *
  * title.zh-CN: 监测整页的滚动
diff --git a/packages/hooks/src/useScroll/demo/demo3.tsx b/packages/hooks/src/useScroll/demo/demo3.tsx
index 39116821..e0e5174d 100644
--- a/packages/hooks/src/useScroll/demo/demo3.tsx
+++ b/packages/hooks/src/useScroll/demo/demo3.tsx
@@ -1,8 +1,8 @@
 /**
- * title: Controll listen on scroll status
+ * title: Custom update
  * desc: listen on scroll event between 100px ~ 200px in vertical direction
  *
- * title.zh-CN: 控制滚动状态的监听
+ * title.zh-CN: 自定义更新
  * desc.zh-CN: 在垂直方向 100px 到 200px 的滚动范围内监听
  */
 
@@ -16,9 +16,8 @@ export default () => {
 
   return (
     <>
-      <div>{JSON.stringify(scroll)}</div>
+      <p>{JSON.stringify(scroll)}</p>
       <div
-        className={'main'}
         style={{
           height: '160px',
           width: '160px',
diff --git a/packages/hooks/src/useScroll/index.en-US.md b/packages/hooks/src/useScroll/index.en-US.md
index 7571389c..b2af37cb 100644
--- a/packages/hooks/src/useScroll/index.en-US.md
+++ b/packages/hooks/src/useScroll/index.en-US.md
@@ -10,8 +10,9 @@ group:
 
 # useScroll
 
-Get the scroll position of an element.
+<Tag lang="en-US" tags="ssr"></Tag>
 
+Get the scroll position of an element.
 
 ## Examples
 
@@ -37,12 +38,11 @@ const position = useScroll(target, shouldUpdate);
 
 | Property     | Description                           | Type                                      | Default                                  |
 |--------------|---------------------------------------|-------------------------------------------|------------------------------------------|
-| target       | DOM element or Ref object             |  `HTMLElement` \| `(() => HTMLElement)` \| `Document` \| `React.MutableRefObject` | `document`                               |
-| shouldUpdate | controll weather update scroll status | `({ top: number, left: number}) => boolean` | `({ top: number, left: number}) => true` |
-
+| target       | DOM element or ref object             |  `Element` \| `Document`  \| `(() => Element)` \| `MutableRefObject<Element>` | `document` |
+| shouldUpdate | Weather update position | `({ top: number, left: number}) => boolean` | `-` |
 
 ### Result
 
 | Property | Description                                 | Type                          |
 |----------|---------------------------------------------|-------------------------------|
-| position | The current scroll position of the element. | `{left: number, top: number}` |
+| position | The current scroll position of the element. |  `{left: number, top: number} \| undefined` |
diff --git a/packages/hooks/src/useScroll/index.ts b/packages/hooks/src/useScroll/index.ts
index 279dabb5..844b7424 100644
--- a/packages/hooks/src/useScroll/index.ts
+++ b/packages/hooks/src/useScroll/index.ts
@@ -1,55 +1,69 @@
 import { useEffect, useState } from 'react';
-import useMemoizedFn from '../useMemoizedFn';
-import { BasicTarget, getTargetElement } from '../utils/dom';
+import useLatest from '../useLatest';
+import type { BasicTarget } from '../utils/dom2';
+import { getTargetElement } from '../utils/dom2';
 
-interface Position {
-  left: number;
-  top: number;
-}
+type Position = { left: number; top: number };
 
-export type Target = BasicTarget<HTMLElement | Document>;
+export type Target = BasicTarget<Element | Document>;
 export type ScrollListenController = (val: Position) => boolean;
 
-function useScroll(target?: Target, shouldUpdate: ScrollListenController = () => true): Position {
-  const [position, setPosition] = useState<Position>({
-    left: NaN,
-    top: NaN,
-  });
+function useScroll(
+  target?: Target,
+  shouldUpdate: ScrollListenController = () => true,
+): Position | undefined {
+  const [position, setPosition] = useState<Position>();
 
-  const shouldUpdatePersist = useMemoizedFn(shouldUpdate);
+  const shouldUpdateRef = useLatest(shouldUpdate);
 
   useEffect(() => {
     const el = getTargetElement(target, document);
-    if (!el) return;
-
-    function updatePosition(currentTarget: Target): void {
-      let newPosition;
-      if (currentTarget === document) {
-        if (!document.scrollingElement) return;
-        newPosition = {
-          left: document.scrollingElement.scrollLeft,
-          top: document.scrollingElement.scrollTop,
-        };
+    if (!el) {
+      return;
+    }
+    const updatePosition = () => {
+      let newPosition: Position;
+      if (el === document) {
+        if (document.scrollingElement) {
+          newPosition = {
+            left: document.scrollingElement.scrollLeft,
+            top: document.scrollingElement.scrollTop,
+          };
+        } else {
+          // When in quirks mode, the scrollingElement attribute returns the HTML body element if it exists and is potentially scrollable, otherwise it returns null.
+          // https://developer.mozilla.org/zh-CN/docs/Web/API/Document/scrollingElement
+          // https://stackoverflow.com/questions/28633221/document-body-scrolltop-firefox-returns-0-only-js
+          newPosition = {
+            left: Math.max(
+              window.pageYOffset,
+              document.documentElement.scrollTop,
+              document.body.scrollTop,
+            ),
+            top: Math.max(
+              window.pageXOffset,
+              document.documentElement.scrollLeft,
+              document.body.scrollLeft,
+            ),
+          };
+        }
       } else {
         newPosition = {
-          left: (currentTarget as HTMLElement).scrollLeft,
-          top: (currentTarget as HTMLElement).scrollTop,
+          left: (el as Element).scrollLeft,
+          top: (el as Element).scrollTop,
         };
       }
-      if (shouldUpdatePersist(newPosition)) setPosition(newPosition);
-    }
+      if (shouldUpdateRef.current(newPosition)) {
+        setPosition(newPosition);
+      }
+    };
 
-    updatePosition(el as Target);
+    updatePosition();
 
-    function listener(event: Event): void {
-      if (!event.target) return;
-      updatePosition(event.target as Target);
-    }
-    el.addEventListener('scroll', listener);
+    el.addEventListener('scroll', updatePosition);
     return () => {
-      el.removeEventListener('scroll', listener);
+      el.removeEventListener('scroll', updatePosition);
     };
-  }, [target, shouldUpdatePersist]);
+  }, [typeof target === 'function' ? undefined : target, shouldUpdateRef]);
 
   return position;
 }
diff --git a/packages/hooks/src/useScroll/index.zh-CN.md b/packages/hooks/src/useScroll/index.zh-CN.md
index d7a61fbb..0701ade8 100644
--- a/packages/hooks/src/useScroll/index.zh-CN.md
+++ b/packages/hooks/src/useScroll/index.zh-CN.md
@@ -10,8 +10,9 @@ group:
 
 # useScroll
 
-获取元素的滚动状态。
+<Tag lang="zh-CN" tags="ssr"></Tag>
 
+监听元素的滚动位置。
 
 ## 代码演示
 
@@ -37,12 +38,12 @@ const position = useScroll(target, shouldUpdate);
 
 | 参数         | 说明                  | 类型                                        | 默认值                                   |
 |--------------|-----------------------|---------------------------------------------|------------------------------------------|
-| target       | DOM 节点或者 Ref 对象 | `HTMLElement` \| `(() => HTMLElement)` \| `Document` \| `React.MutableRefObject`   | `document`                               |
-| shouldUpdate | 控制是否更新滚动信息  | `({ top: number, left: number}) => boolean` | `({ top: number, left: number}) => true` |
+| target       | DOM 节点或者 ref | `Element` \| `Document`  \| `(() => Element)` \| `MutableRefObject<Element>`   | `document` |
+| shouldUpdate | 控制是否更新滚动信息  | `({ top: number, left: number}) => boolean` | - |
 
 
 ### Result
 
 | 参数     | 说明                   | 类型                          |
 |----------|------------------------|-------------------------------|
-| position | 滚动容器当前的滚动位置 | `{left: number, top: number}` |
+| position | 滚动容器当前的滚动位置 | `{left: number, top: number} \| undefined` |
diff --git a/packages/hooks/src/useSize/index.zh-CN.md b/packages/hooks/src/useSize/index.zh-CN.md
index 2c40f32a..df11584c 100644
--- a/packages/hooks/src/useSize/index.zh-CN.md
+++ b/packages/hooks/src/useSize/index.zh-CN.md
@@ -34,7 +34,7 @@ const size = useSize(target);
 
 | 参数    | 说明                                         | 类型                   | 默认值 |
 |---------|----------------------------------------------|------------------------|--------|
-| target | DOM 节点或者 Ref  | `Element` \| `(() => Element)` \| `MutableRefObject<Element>` | -      |
+| target | DOM 节点或者 ref  | `Element` \| `(() => Element)` \| `MutableRefObject<Element>` | -      |
 
 ### 结果
 


Test patch:
diff --git a/packages/hooks/src/useScroll/__tests__/index.test.ts b/packages/hooks/src/useScroll/__tests__/index.test.ts
index a8fab44c..dd447396 100644
--- a/packages/hooks/src/useScroll/__tests__/index.test.ts
+++ b/packages/hooks/src/useScroll/__tests__/index.test.ts
@@ -6,8 +6,7 @@ describe('useScroll', () => {
     expect(useScroll).toBeDefined();
   });
   it('document body', () => {
-    const hook = renderHook(() => useScroll(document.body));
-    expect(hook.result.current.left).toBe(0);
-    expect(hook.result.current.top).toBe(0);
+    const hook = renderHook(() => useScroll(document));
+    expect(hook.result.current).toEqual({ left: 0, top: 0 });
   });
 });