Repository: alibaba/hooks
Pull request #1088 (2021-08-01 12:50:59)

refactor: useVirtualList
<!--
First of all, thank you for your contribution! 😄

New feature please send a pull request to next branch, and rest to master branch.
Pull requests will be merged after one of the collaborators approve.
Please makes sure that these forms are filled before submitting your pull request, thank you!
-->

[[中文版模板 / Chinese template](https://github.com/alibaba/hooks/blob/master/.github/PULL_REQUEST_TEMPLATE/pr_cn.md)]

### 🤔 This is a ...

- [ ] New feature
- [ ] Bug fix
- [ ] Site / documentation update
- [ ] Demo update
- [ ] TypeScript definition update
- [ ] Bundle size optimization
- [ ] Performance optimization
- [ ] Enhancement feature
- [ ] Internationalization
- [x] Refactoring
- [ ] Code style optimization
- [ ] Test Case
- [ ] Branch merge
- [ ] Other (about what?)

### 🔗 Related issue link

<!--
1. Describe the source of requirement, like related issue link.
-->

### 💡 Background and solution

<!--
1. Describe the problem and the scenario.
2. GIF or snapshot should be provided if includes UI/interactive modification.
3. How to fix the problem, and list final API implementation and usage sample if that is a new feature.
-->

### 📝 Changelog

<!--
Describe changes from the user side, and list all potential break changes or other risks.
--->

| Language   | Changelog |
| ---------- | --------- |
| 🇺🇸 English |           |
| 🇨🇳 Chinese |           |

### ☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

- [x] Doc is updated/provided or not needed
- [x] Demo is updated/provided or not needed
- [x] TypeScript definition is updated/provided or not needed
- [x] Changelog is provided or not needed



Patch:
diff --git a/.gitignore b/.gitignore
index 1219e7ed..2d0c9b01 100755
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@ page
 lerna-debug.log
 tsconfig.tsbuildinfo
 packages/hooks/README.md
+yarn-error.log
diff --git a/packages/hooks/src/useDrag/index.ts b/packages/hooks/src/useDrag/index.ts
index 47763472..61a926df 100644
--- a/packages/hooks/src/useDrag/index.ts
+++ b/packages/hooks/src/useDrag/index.ts
@@ -1,3 +1,5 @@
+import useMemoizedFn from '../useMemoizedFn';
+
 type getDragPropsFn = (data: any) => {
   draggable: 'true';
   key?: string;
@@ -5,37 +7,33 @@ type getDragPropsFn = (data: any) => {
   onDragEnd: (e: React.DragEvent) => void;
 };
 
-interface IConfig {
-  onDragStart?: (data: any, e: React.DragEvent) => void;
-  onDragEnd?: (data: any, e: React.DragEvent) => void;
+interface Options<T> {
+  onDragStart?: (data: T, e: React.DragEvent) => void;
+  onDragEnd?: (data: T, e: React.DragEvent) => void;
   /**
-   * 是否在getProps方法返回的对象中包含默认的key
+   * 是否在 getProps 方法返回的对象中包含默认的 key
    *
    * @default true
    */
   getPropsWithKey?: boolean;
 }
 
-const useDrag = (config?: IConfig): getDragPropsFn => {
-  const getProps = (data: any) => {
+const useDrag = <T = any>(options?: Options<T>): getDragPropsFn => {
+  const getProps = (data: T) => {
     return {
-      key: config && config.getPropsWithKey === false ? undefined : JSON.stringify(data),
+      key: options?.getPropsWithKey === false ? undefined : JSON.stringify(data),
       draggable: 'true' as const,
       onDragStart: (e: React.DragEvent) => {
-        if (config && config.onDragStart) {
-          config.onDragStart(data, e);
-        }
+        options?.onDragStart?.(data, e);
         e.dataTransfer.setData('custom', JSON.stringify(data));
       },
       onDragEnd: (e: React.DragEvent) => {
-        if (config && config.onDragEnd) {
-          config.onDragEnd(data, e);
-        }
+        options?.onDragEnd?.(data, e);
       },
     };
   };
 
-  return getProps;
+  return useMemoizedFn(getProps);
 };
 
 export default useDrag;
diff --git a/packages/hooks/src/useDrop/demo/demo1.tsx b/packages/hooks/src/useDrop/demo/demo1.tsx
index 7ecabb3d..361294eb 100644
--- a/packages/hooks/src/useDrop/demo/demo1.tsx
+++ b/packages/hooks/src/useDrop/demo/demo1.tsx
@@ -1,5 +1,5 @@
 /**
- * title: Default usage
+ * title: Basic usage
  * desc: The drop area can accept files, uri, text or one of the boxes below.
  *
  * title.zh-CN: 基础用法
@@ -19,7 +19,7 @@ export default () => {
       setDragging(null);
     },
   });
-  const [props, { isHovering }] = useDrop({
+  const [props, isHovering] = useDrop({
     onText: (text, e) => {
       console.log(e);
       alert(`'text: ${text}' dropped`);
diff --git a/packages/hooks/src/useDrop/index.en-US.md b/packages/hooks/src/useDrop/index.en-US.md
index 49690b23..b372ca80 100644
--- a/packages/hooks/src/useDrop/index.en-US.md
+++ b/packages/hooks/src/useDrop/index.en-US.md
@@ -6,11 +6,12 @@ nav:
 group:
   title: UI
   path: /ui
-  order: 9
 ---
 
 # useDrop & useDrag
 
+<Tag lang="zh-CN" tags="ssr"></Tag>
+
 A pair of hooks to help you manage data transfer between drag and drop
 
 > useDrop can be used alone to accept file, text or uri dropping.
@@ -21,47 +22,51 @@ A pair of hooks to help you manage data transfer between drag and drop
 
 ## Examples
 
-### Default Usage
+### Basic Usage
 
 <code src="./demo/demo1.tsx" />
 
 ## API
 
+### useDrag
+
 ```typescript
-const getDragProps = useDrag({ onDragStart, onDragEnd });
+const getDragProps = useDrag<T>({ onDragStart, onDragEnd, getPropsWithKey });
+```
+
+#### Result
+
+| Property     | Description                                                                                  | Type                    |
+|--------------|----------------------------------------------------------------------------------------------|-------------------------|
+| getDragProps | A function that accept a content as dragging values and return props passed to a dom element | `(content: T) => props` |
+
+
+#### Params
 
-<div {...getDragProps(id)}>draggable</div>
+| Property        | Description                                                                  | Type                          | Default |
+|-----------------|------------------------------------------------------------------------------|-------------------------------|---------|
+| onDragStart     | The callback when a dragging is started                                      | `(data: T, e: Event) => void` | -       |
+| onDragEnd       | The callback when a dragging is ended                                        | `(data: T, e: Event) => void` | -       |
+| getPropsWithKey | Whether to include the default key in the object returned by getprops method | `boolean`                     | -       |
 
-const [ props, { isHovering } ] = useDrop({
+### useDrop
+
+```typescript
+const [ props, isHovering ] = useDrop({
   onText: (text: string, e: Event) => void,
   onFiles: (files: File[], e: Event) => void,
   onUri: (uri: string, e: Event) => void,
   onDom: (content: any, e: Event) => void
 });
 ```
-
-### useDrag Result
-
-| Property     | Description                                                                                  | Type                      |
-|--------------|----------------------------------------------------------------------------------------------|---------------------------|
-| getDragProps | A function that accept a content as dragging values and return props passed to a dom element | `(content: any) => props` |
-
-### useDrop Result
+#### Result
 
 | Property   | Description                                             | Type      |
 |------------|---------------------------------------------------------|-----------|
 | props      | Props passed to the drop area                           | -         |
 | isHovering | Whether the dragging element is on top of the drop area | `boolean` |
 
-### useDrag Params
-
-| 参数        | 说明                                    | 类型                            | 默认值 |
-|-------------|-----------------------------------------|---------------------------------|--------|
-| onDragStart | The callback when a dragging is started | `(data: any, e: Event) => void` | -      |
-| onDragEnd   | The callback when a dragging is ended   | `(data: any, e: Event) => void` | -      |
-| getPropsWithKey | Whether to include the default key in the object returned by getprops method | `boolean` | - |
-
-### useDrop Params
+#### Params
 
 | Property | Description                         | Type                                | Default |
 |----------|-------------------------------------|-------------------------------------|---------|
diff --git a/packages/hooks/src/useDrop/index.ts b/packages/hooks/src/useDrop/index.ts
index c5415c9e..42a8a10d 100644
--- a/packages/hooks/src/useDrop/index.ts
+++ b/packages/hooks/src/useDrop/index.ts
@@ -1,8 +1,5 @@
-import { useMemo, useState, useRef, useCallback } from 'react';
-
-export interface DropAreaState {
-  isHovering: boolean;
-}
+import { useCallback, useMemo, useState } from 'react';
+import useLatest from '../useLatest';
 
 export interface DropProps {
   onDragOver: React.DragEventHandler;
@@ -45,10 +42,11 @@ const getProps = (
   },
 });
 
-const useDrop = (options: DropAreaOptions = {}): [DropProps, DropAreaState] => {
-  const optionsRef = useRef(options);
-  optionsRef.current = options;
+const useDrop = (options: DropAreaOptions = {}): [DropProps, boolean] => {
+  const optionsRef = useLatest(options);
+
   const [isHovering, setIsHovering] = useState<boolean>(false);
+
   const callback = useCallback(
     (dataTransfer: DataTransfer, event: React.DragEvent | React.ClipboardEvent) => {
       const uri = dataTransfer.getData('text/uri-list');
@@ -84,12 +82,12 @@ const useDrop = (options: DropAreaOptions = {}): [DropProps, DropAreaState] => {
     [],
   );
 
-  const props: DropProps = useMemo(() => getProps(callback, setIsHovering), [
-    callback,
-    setIsHovering,
-  ]);
+  const props: DropProps = useMemo(
+    () => getProps(callback, setIsHovering),
+    [callback, setIsHovering],
+  );
 
-  return [props, { isHovering }];
+  return [props, isHovering];
 };
 
 export default useDrop;
diff --git a/packages/hooks/src/useDrop/index.zh-CN.md b/packages/hooks/src/useDrop/index.zh-CN.md
index 5300d52a..1785de9f 100644
--- a/packages/hooks/src/useDrop/index.zh-CN.md
+++ b/packages/hooks/src/useDrop/index.zh-CN.md
@@ -6,18 +6,19 @@ nav:
 group:
   title: UI
   path: /ui
-  order: 9
 ---
 
 # useDrop & useDrag
 
-一对帮助你处理在拖拽中进行数据转移的 hooks
+<Tag lang="zh-CN" tags="ssr"></Tag>
+
+处理元素拖拽的 Hook。
 
 > useDrop 可以单独使用来接收文件、文字和网址的拖拽。
 >
 > useDrag 允许一个 dom 节点被拖拽，需要配合 useDrop 使用。
 >
-> 向节点内触发粘贴时也会被视为拖拽的内容
+> 向节点内触发粘贴动作也会被视为拖拽。
 
 ## 代码演示
 
@@ -27,41 +28,44 @@ group:
 
 ## API
 
+### useDrag
+
 ```typescript
-const getDragProps = useDrag({ onDragStart, onDragEnd });
+const getDragProps = useDrag<T>({ onDragStart, onDragEnd, getPropsWithKey });
+```
+
+#### Result
+
+| 参数         | 说明                                                                                             | 类型                    |
+|--------------|--------------------------------------------------------------------------------------------------|-------------------------|
+| getDragProps | 一个接收拖拽的值，并返回需要透传给被拖拽节点 props 的方法， 默认包含一个由 data 序列化得到的 key | `(content: T) => props` |
+
+#### Params
+
+| 参数            | 说明                                                     | 类型                          | 默认值 |
+|-----------------|----------------------------------------------------------|-------------------------------|--------|
+| onDragStart     | 开始拖拽的回调                                           | `(data: T, e: Event) => void` | -      |
+| onDragEnd       | 结束拖拽的回调                                           | `(data: T, e: Event) => void` | -      |
+| getPropsWithKey | 是否在 getProps 方法返回的对象中包含默认的 key，默认包含 | `boolean`                     | -      |
 
-<div {...getDragProps(id)}>draggable</div>
+### useDrop
 
-const [ props, { isHovering } ] = useDrop({
+```typescript
+const [props, isHovering] = useDrop({
   onText: (text: string, e: Event) => void,
   onFiles: (files: File[], e: Event) => void,
   onUri: (uri: string, e: Event) => void,
   onDom: (content: any, e: Event) => void
 });
 ```
-
-### useDrag Result
-
-| 参数         | 说明                                                      | 类型                      |
-|--------------|-----------------------------------------------------------|---------------------------|
-| getDragProps | 一个接收拖拽的值，并返回需要透传给被拖拽节点 props 的方法， 默认包含一个由 data 序列化得到的key | `(content: any) => props` |
-
-### useDrop Result
+#### Result
 
 | 参数       | 说明                                    | 类型      |
 |------------|-----------------------------------------|-----------|
 | props      | 需要透传给接受拖拽区域 dom 节点的 props | -         |
 | isHovering | 是否是拖拽中，且光标处于释放区域内      | `boolean` |
 
-### useDrag Params
-
-| 参数        | 说明           | 类型                            | 默认值 |
-|-------------|----------------|---------------------------------|--------|
-| onDragStart | 开始拖拽的回调 | `(data: any, e: Event) => void` | -      |
-| onDragEnd   | 结束拖拽的回调 | `(data: any, e: Event) => void` | -      |
-| getPropsWithKey | 是否在 getProps 方法返回的对象中包含默认的 key，默认包含 | `boolean` | - |
-
-### useDrop Params
+#### Params
 
 | 参数    | 说明                      | 类型                                | 默认值 |
 |---------|---------------------------|-------------------------------------|--------|
diff --git a/packages/hooks/src/useFavicon/index.en-US.md b/packages/hooks/src/useFavicon/index.en-US.md
index df7248be..48967303 100644
--- a/packages/hooks/src/useFavicon/index.en-US.md
+++ b/packages/hooks/src/useFavicon/index.en-US.md
@@ -12,7 +12,7 @@ group:
 
 <Tag lang="en-US" tags="ssr"></Tag>
 
-Set page favicon.
+A hook that sets the favicon of the page.
 
 ## Example
 
diff --git a/packages/hooks/src/useHistoryTravel/demo/demo1.tsx b/packages/hooks/src/useHistoryTravel/demo/demo1.tsx
index 928d1fbc..7a9b7dea 100644
--- a/packages/hooks/src/useHistoryTravel/demo/demo1.tsx
+++ b/packages/hooks/src/useHistoryTravel/demo/demo1.tsx
@@ -1,6 +1,6 @@
 /**
  * title: Basic usage
- * desc: redo and undo operations，click back and forward after input something.
+ * desc: Redo and undo operations，click back and forward after input something.
  *
  * title.zh-CN: 基础用法
  * desc.zh-CN: 撤销跟重做操作，输入内容后，点击 back 和 forward。
diff --git a/packages/hooks/src/useHistoryTravel/demo/demo2.tsx b/packages/hooks/src/useHistoryTravel/demo/demo2.tsx
index 0fb1353b..5ed8fdd8 100644
--- a/packages/hooks/src/useHistoryTravel/demo/demo2.tsx
+++ b/packages/hooks/src/useHistoryTravel/demo/demo2.tsx
@@ -1,25 +1,18 @@
 /**
  * title: TodoList
- * desc: redo and undo operations
+ * desc: Redo and undo operations
  *
  * title.zh-CN: 可撤销恢复的 Todo List
- * desc.zh: 可以实现撤销恢复等操作。
+ * desc.zh-CN: 可以实现撤销恢复等操作。
  */
 
 import { useHistoryTravel } from 'ahooks';
 import React, { useState } from 'react';
 
 export default () => {
-  const {
-    value,
-    setValue,
-    backLength,
-    forwardLength,
-    back,
-    forward,
-    go,
-    reset,
-  } = useHistoryTravel(['do homework']);
+  const { value, setValue, backLength, forwardLength, back, forward, go, reset } = useHistoryTravel(
+    ['do homework'],
+  );
 
   const [inputValue, setInputValue] = useState('');
   const [step, setStep] = useState(0);
diff --git a/packages/hooks/src/useHistoryTravel/index.en-US.md b/packages/hooks/src/useHistoryTravel/index.en-US.md
index aae5d28d..25e18b54 100644
--- a/packages/hooks/src/useHistoryTravel/index.en-US.md
+++ b/packages/hooks/src/useHistoryTravel/index.en-US.md
@@ -10,6 +10,8 @@ group:
 
 # useHistoryTravel
 
+<Tag lang="en-US" tags="ssr&crossPlatform"></Tag>
+
 A hook to manage state change history. It provides encapsulation methods to travel through the history.
 
 ## Examples
@@ -25,24 +27,32 @@ A hook to manage state change history. It provides encapsulation methods to trav
 ## API
 
 ```typescript
-const { value, setValue, backLength, forwardLength, go, back, forward, reset } = useHistoryTravel<T>(initialValue?: T);
+const { 
+  value, 
+  setValue, 
+  backLength, 
+  forwardLength,
+  go, 
+  back, 
+  forward 
+} = useHistoryTravel<T>(initialValue?: T);
 ```
 
 ### Params
 
-| Property      | Description            | Type | Default |
-|---------------|------------------------|------|---------|
-| initialValue? | optional initial value | `T`  | -       |
+| Property     | Description            | Type | Default |
+|--------------|------------------------|------|---------|
+| initialValue | Optional initial value | `T`  | -       |
 
 ### Result
 
 | Property      | Description                                                                       | Type                          |
 |---------------|-----------------------------------------------------------------------------------|-------------------------------|
-| value         | current value                                                                     | `T`                           |
-| setValue      | function to set value                                                             | `T => void`                   |
-| backLength    | the length of backward history                                                    | `number`                      |
-| forwardLength | the length of forward history                                                     | `number`                      |
-| go            | move between the history, move backward on step < 0，and move forward on step > 0 | `(step: number) => void`      |
-| back          | move one step backward in history                                                 | `() => void`                  |
-| foward        | move one step forward in history                                                  | `() => void`                  |
-| reset         | reset history to initial value by default or provide a new initial value.         | (newInitialValue?: T) => void |
+| value         | Current value                                                                     | `T`                           |
+| setValue      | Function to set value                                                             | `(value: T) => void`          |
+| backLength    | The length of backward history                                                    | `number`                      |
+| forwardLength | The length of forward history                                                     | `number`                      |
+| go            | Move between the history, move backward on step < 0，and move forward on step > 0 | `(step: number) => void`      |
+| back          | Move one step backward in history                                                 | `() => void`                  |
+| foward        | Move one step forward in history                                                  | `() => void`                  |
+| reset         | Reset history to initial value by default or provide a new initial value.         | (newInitialValue?: T) => void |
diff --git a/packages/hooks/src/useHistoryTravel/index.ts b/packages/hooks/src/useHistoryTravel/index.ts
index dee99b52..478c903d 100644
--- a/packages/hooks/src/useHistoryTravel/index.ts
+++ b/packages/hooks/src/useHistoryTravel/index.ts
@@ -1,4 +1,5 @@
-import { useState, useCallback, useRef } from 'react';
+import { useRef, useState } from 'react';
+import useMemoizedFn from '../useMemoizedFn';
 
 interface IData<T> {
   present?: T;
@@ -25,7 +26,7 @@ const split = <T>(step: number, targetArr: T[]) => {
   return {
     _current: targetArr[index],
     _before: targetArr.slice(0, index),
-    _after: targetArr.slice(index + 1)
+    _after: targetArr.slice(index + 1),
   };
 };
 
@@ -33,95 +34,80 @@ export default function useHistoryTravel<T>(initialValue?: T) {
   const [history, setHistory] = useState<IData<T | undefined>>({
     present: initialValue,
     past: [],
-    future: []
+    future: [],
   });
 
   const { present, past, future } = history;
 
   const initialValueRef = useRef(initialValue);
 
-  const reset = useCallback(
-    (...params: any[]) => {
-      const _initial = params.length > 0 ? params[0] : initialValueRef.current;
-      initialValueRef.current = _initial;
+  const reset = (...params: any[]) => {
+    const _initial = params.length > 0 ? params[0] : initialValueRef.current;
+    initialValueRef.current = _initial;
 
-      setHistory({
-        present: _initial,
-        future: [],
-        past: []
-      });
-    },
-    [history, setHistory]
-  );
+    setHistory({
+      present: _initial,
+      future: [],
+      past: [],
+    });
+  };
 
-  const updateValue = useCallback(
-    (val: T) => {
-      setHistory({
-        present: val,
-        future: [],
-        past: [...past, present]
-      });
-    },
-    [history, setHistory]
-  );
+  const updateValue = (val: T) => {
+    setHistory({
+      present: val,
+      future: [],
+      past: [...past, present],
+    });
+  };
 
-  const _forward = useCallback(
-    (step: number = 1) => {
-      if (future.length === 0) {
-        return;
-      }
-      const { _before, _current, _after } = split(step, future);
-      setHistory({
-        past: [...past, present, ..._before],
-        present: _current,
-        future: _after
-      });
-    },
-    [history, setHistory]
-  );
+  const _forward = (step: number = 1) => {
+    if (future.length === 0) {
+      return;
+    }
+    const { _before, _current, _after } = split(step, future);
+    setHistory({
+      past: [...past, present, ..._before],
+      present: _current,
+      future: _after,
+    });
+  };
 
-  const _backward = useCallback(
-    (step: number = -1) => {
-      if (past.length === 0) {
-        return;
-      }
+  const _backward = (step: number = -1) => {
+    if (past.length === 0) {
+      return;
+    }
 
-      const { _before, _current, _after } = split(step, past);
-      setHistory({
-        past: _before,
-        present: _current,
-        future: [..._after, present, ...future]
-      });
-    },
-    [history, setHistory]
-  );
+    const { _before, _current, _after } = split(step, past);
+    setHistory({
+      past: _before,
+      present: _current,
+      future: [..._after, present, ...future],
+    });
+  };
 
-  const go = useCallback(
-    (step: number) => {
-      const stepNum = typeof step === 'number' ? step : Number(step);
-      if (stepNum === 0) {
-        return;
-      }
-      if (stepNum > 0) {
-        return _forward(stepNum);
-      }
-      _backward(stepNum);
-    },
-    [_backward, _forward]
-  );
+  const go = (step: number) => {
+    const stepNum = typeof step === 'number' ? step : Number(step);
+    if (stepNum === 0) {
+      return;
+    }
+    if (stepNum > 0) {
+      return _forward(stepNum);
+    }
+    _backward(stepNum);
+  };
 
   return {
     value: present,
-    setValue: updateValue,
     backLength: past.length,
     forwardLength: future.length,
-    go,
-    back: useCallback(() => {
+    setValue: useMemoizedFn(updateValue),
+    go: useMemoizedFn(go),
+    back: useMemoizedFn(() => {
       go(-1);
-    }, [go]),
-    forward: useCallback(() => {
+    }),
+    forward: useMemoizedFn(() => {
       go(1);
-    }, [go]),
-    reset
+    }),
+    reset: useMemoizedFn(reset),
   };
 }
diff --git a/packages/hooks/src/useHistoryTravel/index.zh-CN.md b/packages/hooks/src/useHistoryTravel/index.zh-CN.md
index bafa0338..99ac501e 100644
--- a/packages/hooks/src/useHistoryTravel/index.zh-CN.md
+++ b/packages/hooks/src/useHistoryTravel/index.zh-CN.md
@@ -10,6 +10,8 @@ group:
 
 # useHistoryTravel
 
+<Tag lang="zh-CN" tags="ssr&crossPlatform"></Tag>
+
 优雅的管理状态变化历史，可以快速在状态变化历史中穿梭 - 前进跟后退。
 
 ## 代码演示
@@ -25,14 +27,22 @@ group:
 ## API
 
 ```typescript
-const { value, setValue, backLength, forwardLength, go, back, forward } = useHistoryTravel<T>(initialValue?: T);
+const { 
+  value, 
+  setValue, 
+  backLength, 
+  forwardLength,
+  go, 
+  back, 
+  forward 
+} = useHistoryTravel<T>(initialValue?: T);
 ```
 
 ### Params
 
-| 参数         | 说明         | 类型 | 默认值 |
-|--------------|--------------|------|--------|
-| initialValue | 可选, 初始值 | `T`  | -      |
+| 参数         | 说明         | 类型  | 默认值 |
+|--------------|--------------|-------|--------|
+| initialValue | 可选, 初始值 | `any` | -      |
 
 
 ### Result
@@ -40,10 +50,10 @@ const { value, setValue, backLength, forwardLength, go, back, forward } = useHis
 | 参数          | 说明                                              | 类型                            |
 |---------------|---------------------------------------------------|---------------------------------|
 | value         | 当前值                                            | `T`                             |
-| setValue      | 设值函数                                          | `T => void`                     |
+| setValue      | 设值函数                                          | `(value: T) => void`            |
 | backLength    | 可回退历史长度                                    | `number`                        |
 | forwardLength | 可前进历史长度                                    | `number`                        |
 | go            | 历史穿梭函数, step < 0 为后退， step > 0 时为前进 | `(step: number) => void`        |
 | back          | 在历史中向后回退一步                              | `() => void`                    |
 | foward        | 在历史中向前前进一步                              | `() => void`                    |
-| reset         | 重置到初始值，或提供一个新的初始值                      | `(newInitialValue?: T) => void` |
+| reset         | 重置到初始值，或提供一个新的初始值                | `(newInitialValue?: T) => void` |
diff --git a/packages/hooks/src/useHover/demo/demo1.tsx b/packages/hooks/src/useHover/demo/demo1.tsx
index 29f4912d..56a5b34a 100644
--- a/packages/hooks/src/useHover/demo/demo1.tsx
+++ b/packages/hooks/src/useHover/demo/demo1.tsx
@@ -1,6 +1,6 @@
 /**
- * title: Default usage
- * desc: Use ref to set element that need listen.
+ * title: Basic usage
+ * desc: Use ref to set element that needs monitoring.
  *
  * title.zh-CN: 基础用法
  * desc.zh-CN: 使用 ref 设置需要需要监听的元素。
diff --git a/packages/hooks/src/useHover/index.en-US.md b/packages/hooks/src/useHover/index.en-US.md
index 5bcaba0b..d8d64740 100644
--- a/packages/hooks/src/useHover/index.en-US.md
+++ b/packages/hooks/src/useHover/index.en-US.md
@@ -12,7 +12,7 @@ group:
 
 <Tag lang="en-US" tags="ssr"></Tag>
 
-Monitor whether the DOM element has a mouse hover.
+A hook that tracks whether the element is being hovered.
 
 ## Examples
 
@@ -47,11 +47,11 @@ const isHovering = useHover(
 
 | Property | Description                                           | Type      | Default |
 |----------|-------------------------------------------------------|-----------|---------|
-| onEnter  | Triggered on hover          | `()=>void` | -       |
-| onLeave  | Triggered on leave     | `()=>void` | -       |
+| onEnter  | Callback to be executed on mouse hover          | `()=>void` | -       |
+| onLeave  | Callback to be executed on mouse leave     | `()=>void` | -       |
 
 ### Result
 
 | Property   | Description                                                 | Type    |
 |------------|-------------------------------------------------------------|---------|
-| isHovering | Whether the element is in hover | `boolean` |
+| isHovering | Whether the element is being hovered | `boolean` |
diff --git a/packages/hooks/src/useIsomorphicLayoutEffect/index.en-US.md b/packages/hooks/src/useIsomorphicLayoutEffect/index.en-US.md
index 5686f288..49445f6d 100644
--- a/packages/hooks/src/useIsomorphicLayoutEffect/index.en-US.md
+++ b/packages/hooks/src/useIsomorphicLayoutEffect/index.en-US.md
@@ -12,19 +12,18 @@ group:
 
 <Tag lang="en-US" tags="ssr&crossPlatform"></Tag>
 
-In SSR mode, the following warning will appear when use useLayoutEffect
+In SSR mode, the following warning will appear when useLayoutEffect is used
 
 > ⚠️ Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://fb.me/react-uselayouteffect-ssr for common fixes.
 
 To avoid this warning, useIsomorphicLayoutEffect can be used instead of useLayoutEffect.
 
-
 The source code of useIsomorphicLayoutEffect is:
 
 ```javascript
 const useIsomorphicLayoutEffect = isBrowser ? useLayoutEffect : useEffect;
 ```
 
-UseEffect is returned in a non-browser environment, and useLayoutEffect is returned in a browser environment.
+Return useLayoutEffect for browser environment and useEffect for other environments.
 
 For more information, please refer to [useLayoutEffect and SSR](https://medium.com/@alexandereardon/uselayouteffect-and-ssr-192986cdcf7a)
\ No newline at end of file
diff --git a/packages/hooks/src/useIsomorphicLayoutEffect/index.zh-CN.md b/packages/hooks/src/useIsomorphicLayoutEffect/index.zh-CN.md
index 01a81466..7fda65d5 100644
--- a/packages/hooks/src/useIsomorphicLayoutEffect/index.zh-CN.md
+++ b/packages/hooks/src/useIsomorphicLayoutEffect/index.zh-CN.md
@@ -16,10 +16,8 @@ group:
 
 > ⚠️ Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://fb.me/react-uselayouteffect-ssr for common fixes.
 
-
 为了避免该警告，可以使用 useIsomorphicLayoutEffect 代替 useLayoutEffect。
 
-
 useIsomorphicLayoutEffect 源码如下：
 
 ```js
diff --git a/packages/hooks/src/useMount/demo/demo1.tsx b/packages/hooks/src/useMount/demo/demo1.tsx
index 6ce30e2c..616c71e3 100644
--- a/packages/hooks/src/useMount/demo/demo1.tsx
+++ b/packages/hooks/src/useMount/demo/demo1.tsx
@@ -1,6 +1,6 @@
 /**
- * title: Default usage
- * desc: The function is called right after the component mount.
+ * title: Basic usage
+ * desc: The function is called right after the component is mounted.
  *
  * title.zh-CN: 基础用法
  * desc.zh-CN: 在组件首次渲染时，执行方法。
diff --git a/packages/hooks/src/useMount/index.en-US.md b/packages/hooks/src/useMount/index.en-US.md
index e11ba9c7..d61dede9 100644
--- a/packages/hooks/src/useMount/index.en-US.md
+++ b/packages/hooks/src/useMount/index.en-US.md
@@ -12,7 +12,7 @@ group:
 
 <Tag lang="en-US" tags="ssr&crossPlatform"></Tag>
 
-A hook that executes a function at mount.
+A hook that executes a function after the component is mounted.
 
 ## Examples
 
@@ -30,4 +30,4 @@ useMount(fn: () => void);
 
 | Property | Description                      | Type         | Default |
 |----------|----------------------------------|--------------|---------|
-| fn       | The function need to be executed | `() => void` | -       |
+| fn       | The function to be executed | `() => void` | -       |
diff --git a/packages/hooks/src/useNetwork/demo/demo1.tsx b/packages/hooks/src/useNetwork/demo/demo1.tsx
index 5226dacc..8f84a23f 100644
--- a/packages/hooks/src/useNetwork/demo/demo1.tsx
+++ b/packages/hooks/src/useNetwork/demo/demo1.tsx
@@ -1,6 +1,6 @@
 /**
- * title: Default usage
- * desc: return network status
+ * title: Basic usage
+ * desc: Return network status
  *
  * title.zh-CN: 基础用法
  * desc.zh-CN: 返回网络状态信息
@@ -14,7 +14,8 @@ export default () => {
 
   return (
     <div>
-      <p>Network：{JSON.stringify(networkState)}</p>
+      <div>Network information: </div>
+      <pre>{JSON.stringify(networkState, null, 2)}</pre>
     </div>
   );
 };
diff --git a/packages/hooks/src/useNetwork/index.en-US.md b/packages/hooks/src/useNetwork/index.en-US.md
index aedf9d99..82d33a8a 100644
--- a/packages/hooks/src/useNetwork/index.en-US.md
+++ b/packages/hooks/src/useNetwork/index.en-US.md
@@ -12,7 +12,7 @@ group:
 
 <Tag lang="en-US" tags="ssr&crossPlatform"></Tag>
 
-A hook that can manage the state of network.
+A hook that tracks the state of network connection.
 
 ## Examples
 
@@ -41,11 +41,11 @@ const result: NetworkState = useNetwork();
 
 | Property | Description                                         | Type                 |
 |----------|--------------------------------------|----------------------|
-| online  | Effective online | `boolean` |
-| since  | Online and offline last change time | `Date` |
-| rtt  | Round-trip time | `number` |
-| type  | Type of connection a device is using to communicate with the network | `bluetooth` \| `cellular` \| `ethernet` \| `none` \| `wifi` \| `wimax` \| `other` \| `unknown` |
-| downlink  | Effective bandwidth estimate in megabits per second | `number` |
-| downlinkMax  | The maximum downlink speed | `number` |
+| online  | Whether connected to network | `boolean` |
+| since  | The last update time since network connection changes | `Date` |
+| rtt  | The effective round-trip time estimate in milliseconds | `number` |
+| type  | The connection type that the user agent is using | `bluetooth` \| `cellular` \| `ethernet` \| `none` \| `wifi` \| `wimax` \| `other` \| `unknown` |
+| downlink  | The effective bandwidth estimate in megabits per second, | `number` |
+| downlinkMax  | An upper bound on the downlink speed of the first network hop | `number` |
 | saveData  | Whether the user agent has set the option to reduce data usage | `boolean` |
-| effectiveType  | The effective type of the connection | `slow-2g` \| `2g` \| `3g` \| `4g` |
+| effectiveType  | The effective connection type | `slow-2g` \| `2g` \| `3g` \| `4g` |
diff --git a/packages/hooks/src/useNetwork/index.ts b/packages/hooks/src/useNetwork/index.ts
index 4a498e0f..33579d24 100644
--- a/packages/hooks/src/useNetwork/index.ts
+++ b/packages/hooks/src/useNetwork/index.ts
@@ -11,6 +11,12 @@ export interface NetworkState {
   effectiveType?: string;
 }
 
+enum NetworkEventType {
+  ONLINE = 'online',
+  OFFLINE = 'offline',
+  CHANGE = 'change',
+}
+
 function getConnection() {
   const nav = navigator as any;
   if (typeof nav !== 'object') return null;
@@ -63,16 +69,16 @@ function useNetwork(): NetworkState {
       }));
     };
 
-    window.addEventListener('online', onOnline);
-    window.addEventListener('offline', onOffline);
+    window.addEventListener(NetworkEventType.ONLINE, onOnline);
+    window.addEventListener(NetworkEventType.OFFLINE, onOffline);
 
     const connection = getConnection();
-    connection?.addEventListener('change', onConnectionChange);
+    connection?.addEventListener(NetworkEventType.CHANGE, onConnectionChange);
 
     return () => {
-      window.removeEventListener('online', onOnline);
-      window.removeEventListener('offline', onOffline);
-      connection?.removeEventListener('change', onConnectionChange);
+      window.removeEventListener(NetworkEventType.ONLINE, onOnline);
+      window.removeEventListener(NetworkEventType.OFFLINE, onOffline);
+      connection?.removeEventListener(NetworkEventType.CHANGE, onConnectionChange);
     };
   }, []);
 
diff --git a/packages/hooks/src/useSize/demo/demo1.tsx b/packages/hooks/src/useSize/demo/demo1.tsx
index 47046cbb..2742ebee 100644
--- a/packages/hooks/src/useSize/demo/demo1.tsx
+++ b/packages/hooks/src/useSize/demo/demo1.tsx
@@ -1,6 +1,6 @@
 /**
- * title: Default usage
- * desc: useSize can receive ref parameters
+ * title: Basic usage
+ * desc: useSize can receive ref as argument
  *
  * title.zh-CN: 基础用法
  * desc.zh-CN: useSize 可以接收 ref 参数
diff --git a/packages/hooks/src/useSize/demo/demo2.tsx b/packages/hooks/src/useSize/demo/demo2.tsx
index 30b2c799..e1f9b5a9 100644
--- a/packages/hooks/src/useSize/demo/demo2.tsx
+++ b/packages/hooks/src/useSize/demo/demo2.tsx
@@ -1,6 +1,6 @@
 /**
  * title: pass in the DOM element
- * desc: useSize can receive dom parameters, and in SSR scenarios, you can pass in function `()=>dom`
+ * desc: useSize can receive a dom element as parameter. In SSR scenarios, you can pass in function `()=>dom`
  *
  * title.zh-CN: 传入 DOM 元素
  * desc.zh-CN: useSize 可以接收 dom，在 SSR 场景可以传入函数 `()=> dom`
diff --git a/packages/hooks/src/useSize/index.en-US.md b/packages/hooks/src/useSize/index.en-US.md
index fbfce161..89f38191 100644
--- a/packages/hooks/src/useSize/index.en-US.md
+++ b/packages/hooks/src/useSize/index.en-US.md
@@ -12,7 +12,7 @@ group:
 
 <Tag lang="en-US" tags="ssr"></Tag>
 
-A hook to observe element's size change.
+A hook that observes size change of an element.
 
 ## Examples
 
diff --git a/packages/hooks/src/useSize/index.ts b/packages/hooks/src/useSize/index.ts
index 9ac9a6cc..d58be5a3 100644
--- a/packages/hooks/src/useSize/index.ts
+++ b/packages/hooks/src/useSize/index.ts
@@ -18,9 +18,10 @@ function useSize(target: BasicTarget): Size | undefined {
 
     const resizeObserver = new ResizeObserver((entries) => {
       entries.forEach((entry) => {
+        const { clientWidth, clientHeight } = entry.target;
         setState({
-          width: entry.target.clientWidth,
-          height: entry.target.clientHeight,
+          width: clientWidth,
+          height: clientHeight,
         });
       });
     });
diff --git a/packages/hooks/src/useUnmount/demo/demo1.tsx b/packages/hooks/src/useUnmount/demo/demo1.tsx
index 98d25418..7296c71f 100644
--- a/packages/hooks/src/useUnmount/demo/demo1.tsx
+++ b/packages/hooks/src/useUnmount/demo/demo1.tsx
@@ -1,6 +1,6 @@
 /**
- * title: Default usage
- * desc: The function is called before the component unmount.
+ * title: Basic usage
+ * desc: The function is called right before the component is unmounted.
  *
  * title.zh-CN: 基础用法
  * desc.zh-CN: 在组件卸载时，执行函数。
diff --git a/packages/hooks/src/useUnmount/index.en-US.md b/packages/hooks/src/useUnmount/index.en-US.md
index 2870d55c..d311bc72 100644
--- a/packages/hooks/src/useUnmount/index.en-US.md
+++ b/packages/hooks/src/useUnmount/index.en-US.md
@@ -13,7 +13,7 @@ group:
 
 <Tag lang="en-US" tags="ssr&crossPlatform"></Tag>
 
-A hook that execute the function at unmount.
+A hook that executes the function right before the component is unmounted.
 
 ## Examples
 
@@ -31,4 +31,4 @@ useUnmount(fn: () => void);
 
 | Property | Description                      | Type         | Default |
 |----------|----------------------------------|--------------|---------|
-| fn       | the function need to be executed | `() => void` | -       |
+| fn       | The function to be executed | `() => void` | -       |
diff --git a/packages/hooks/src/useUpdate/demo/demo1.tsx b/packages/hooks/src/useUpdate/demo/demo1.tsx
index 9aed693b..2f72c87b 100644
--- a/packages/hooks/src/useUpdate/demo/demo1.tsx
+++ b/packages/hooks/src/useUpdate/demo/demo1.tsx
@@ -1,3 +1,11 @@
+/**
+ * title: Basic usage
+ * desc: Forces component to re-render.
+ *
+ * title.zh-CN: 基础用法
+ * desc.zh-CN: 强制组件刷新
+ */
+
 import React from 'react';
 import { useUpdate } from 'ahooks';
 
diff --git a/packages/hooks/src/useUpdate/index.en-US.md b/packages/hooks/src/useUpdate/index.en-US.md
index 2f6d6b5c..a1af4fd2 100644
--- a/packages/hooks/src/useUpdate/index.en-US.md
+++ b/packages/hooks/src/useUpdate/index.en-US.md
@@ -12,7 +12,7 @@ group:
 
 <Tag lang="en-US" tags="ssr&crossPlatform"></Tag>
 
-A hook that executes a function at update.
+A hook that returns a function which can be used to force the component to re-render.
 
 ## Examples
 
diff --git a/packages/hooks/src/useVirtualList/demo/demo2.tsx b/packages/hooks/src/useVirtualList/demo/demo2.tsx
index 040f9e32..9d7b74ef 100644
--- a/packages/hooks/src/useVirtualList/demo/demo2.tsx
+++ b/packages/hooks/src/useVirtualList/demo/demo2.tsx
@@ -1,6 +1,6 @@
 /**
  * title: Dynamic item height
- * desc: specify item height dynamically.
+ * desc: Specify item height dynamically.
  *
  * title.zh-CN: 动态元素高度
  * desc.zh-CN: 动态指定每个元素的高度
diff --git a/packages/hooks/src/useVirtualList/index.en-US.md b/packages/hooks/src/useVirtualList/index.en-US.md
index 6b8b7948..a507fa05 100644
--- a/packages/hooks/src/useVirtualList/index.en-US.md
+++ b/packages/hooks/src/useVirtualList/index.en-US.md
@@ -10,6 +10,8 @@ group:
 
 # useVirtualList
 
+<Tag lang="en-US" tags="ssr"></Tag>
+
 A hook that allows you to use virtual list to render huge chunks of list data.
 
 ## Examples
@@ -25,30 +27,30 @@ A hook that allows you to use virtual list to render huge chunks of list data.
 ## API
 
 ```typescript
-const result:Result = useVirtualList(originalList: any[], options: Options);
+const result: Result = useVirtualList(originalList: T[], options: Options);
 ```
 
 
 ### Params
 
-| Property     | Description                                           | Type | Default |
-|--------------|-------------------------------------------------------|------|---------|
-| originalList | The original list that contains a lot of data entries | `T[]`  | `[]`      |
-| options      | Optional configuration item, see Options              | -    | -       |
+| Property     | Description                                           | Type      | Default |
+|--------------|-------------------------------------------------------|-----------|---------|
+| originalList | The original list that contains a lot of data entries | `T[]`     | `[]`    |
+| options      | Optional configuration item, see Options              | `Options` | -       |
 
 
 ### Options
 
-| Property   | Description                                                             | Type   | Default |
-|------------|-------------------------------------------------------------------------|--------|---------|
-| itemHeight | item height, accept a pixel value or a function that returns the height |  `number` \| `((index: number) => number)` | -       |
-| overscan   | the extra buffer items outside of the view area                         | `number` | `5`      |
+| Property   | Description                                                             | Type                                      | Default |
+|------------|-------------------------------------------------------------------------|-------------------------------------------|---------|
+| itemHeight | Item height, accept a pixel value or a function that returns the height | `number` \| `((index: number) => number)` | -       |
+| overscan   | The extra buffer items outside of the view area                         | `number`                                  | `5`     |
 
 ### Result
 
-| Property       | Description                                            | Type                       |
-|----------------|--------------------------------------------------------|----------------------------|
+| Property       | Description                                            | Type                         |
+|----------------|--------------------------------------------------------|------------------------------|
 | list           | The current portion of data need to be rendered to DOM | `{data: T, index: number}[]` |
-| containerProps | the props of outter container                          | `object`                        |
-| wrapperProps   | the props of inner wrapper                             | `object`                         |
-| scrollTo       | scroll to specific index                               | `(index: number) => void`    |
+| containerProps | The props of outter container                          | `object`                     |
+| wrapperProps   | The props of inner wrapper                             | `object`                     |
+| scrollTo       | Scroll to specific index                               | `(index: number) => void`    |
diff --git a/packages/hooks/src/useVirtualList/index.ts b/packages/hooks/src/useVirtualList/index.ts
index 5fa399f4..093fcd8c 100644
--- a/packages/hooks/src/useVirtualList/index.ts
+++ b/packages/hooks/src/useVirtualList/index.ts
@@ -1,12 +1,13 @@
 import { useEffect, useMemo, useRef, useState } from 'react';
+import useMemoizedFn from '../useMemoizedFn';
 import useSize from '../useSize';
 
-export interface OptionType {
+export interface Options {
   itemHeight: number | ((index: number) => number);
   overscan?: number;
 }
 
-export default <T = any>(list: T[], options: OptionType) => {
+const useVirtualList = <T = any>(list: T[], options: Options) => {
   const containerRef = useRef<HTMLElement | null>();
   const size = useSize(containerRef);
   // 暂时禁止 cache
@@ -15,7 +16,7 @@ export default <T = any>(list: T[], options: OptionType) => {
   const { itemHeight, overscan = 5 } = options;
 
   if (!itemHeight) {
-    console.warn('please enter a valid itemHeight');
+    console.warn('useVirtualList: please enter a valid itemHeight');
   }
 
   const getViewCapacity = (containerHeight: number) => {
@@ -112,15 +113,15 @@ export default <T = any>(list: T[], options: OptionType) => {
       data: ele,
       index: index + state.start,
     })),
-    scrollTo,
+    scrollTo: useMemoizedFn(scrollTo),
     containerProps: {
-      ref: (ele: any) => {
+      ref: useMemoizedFn((ele: any) => {
         containerRef.current = ele;
-      },
-      onScroll: (e: any) => {
+      }),
+      onScroll: useMemoizedFn((e: any) => {
         e.preventDefault();
         calculateRange();
-      },
+      }),
       style: { overflowY: 'auto' as const },
     },
     wrapperProps: {
@@ -132,3 +133,5 @@ export default <T = any>(list: T[], options: OptionType) => {
     },
   };
 };
+
+export default useVirtualList;
diff --git a/packages/hooks/src/useVirtualList/index.zh-CN.md b/packages/hooks/src/useVirtualList/index.zh-CN.md
index 2e6f6ab2..6e1834fd 100644
--- a/packages/hooks/src/useVirtualList/index.zh-CN.md
+++ b/packages/hooks/src/useVirtualList/index.zh-CN.md
@@ -10,6 +10,8 @@ group:
 
 # useVirtualList
 
+<Tag lang="zh-CN" tags="ssr"></Tag>
+
 提供虚拟化列表能力的 Hook，用于解决展示海量数据渲染时首屏渲染缓慢和滚动卡顿问题。
 
 ## 代码演示
@@ -25,29 +27,29 @@ group:
 ## API
 
 ```typescript
-const result:Result = useVirtualList(originalList: any[], options: Options);
+const result: Result = useVirtualList(originalList: T[], options: Options);
 ```
 
 ### Params
 
-| 参数         | 说明                   | 类型 | 默认值 |
-|--------------|------------------------|------|--------|
-| originalList | 包含大量数据的列表     | `T[]`  | `[]`     |
-| options      | 可选配置项，见 Options | -    | -      |
+| 参数         | 说明                   | 类型      | 默认值 |
+|--------------|------------------------|-----------|--------|
+| originalList | 包含大量数据的列表     | `T[]`     | `[]`   |
+| options      | 可选配置项，见 Options | `Options` | -      |
 
 
 ### Options
 
-| 参数       | 说明                                                   | 类型   | 默认值 |
-|------------|--------------------------------------------------------|--------|--------|
-| itemHeight | 行高度，静态高度可以直接写入像素值，动态高度可传入函数 |   `number` \| `((index: number) => number)`  | -      |
-| overscan   | 视区上、下额外展示的 dom 节点数量                      | `number` | `5`     |
+| 参数       | 说明                                                   | 类型                                      | 默认值 |
+|------------|--------------------------------------------------------|-------------------------------------------|--------|
+| itemHeight | 行高度，静态高度可以直接写入像素值，动态高度可传入函数 | `number` \| `((index: number) => number)` | -      |
+| overscan   | 视区上、下额外展示的 dom 节点数量                      | `number`                                  | `5`    |
 
 ### Result
 
-| 参数           | 说明                      | 类型                       |
-|----------------|---------------------------|----------------------------|
+| 参数           | 说明                      | 类型                         |
+|----------------|---------------------------|------------------------------|
 | list           | 当前需要展示的列表内容    | `{data: T, index: number}[]` |
-| containerProps | 滚动容器的 props          | `object`                         |
-| wrapperProps   | children 外层包裹器 props | `object`                         |
+| containerProps | 滚动容器的 props          | `object`                     |
+| wrapperProps   | children 外层包裹器 props | `object`                     |
 | scrollTo       | 快速滚动到指定 index      | `(index: number) => void`    |
diff --git a/packages/hooks/src/useWebSocket/demo/demo1.tsx b/packages/hooks/src/useWebSocket/demo/demo1.tsx
index 3904caee..9fe3ed50 100644
--- a/packages/hooks/src/useWebSocket/demo/demo1.tsx
+++ b/packages/hooks/src/useWebSocket/demo/demo1.tsx
@@ -23,9 +23,10 @@ export default () => {
     'wss://echo.websocket.org',
   );
 
-  messageHistory.current = useMemo(() => messageHistory.current.concat(latestMessage), [
-    latestMessage,
-  ]);
+  messageHistory.current = useMemo(
+    () => messageHistory.current.concat(latestMessage),
+    [latestMessage],
+  );
 
   return (
     <div>
@@ -47,7 +48,7 @@ export default () => {
       </button>
       {/* connect */}
       <button onClick={() => connect && connect()} disabled={readyState === ReadyState.Open}>
-        📞 connect
+        {readyState === ReadyState.Connecting ? 'connecting' : '📞 connect'}
       </button>
       <div style={{ marginTop: 8 }}>readyState: {readyState}</div>
       <div style={{ marginTop: 8 }}>
diff --git a/packages/hooks/src/useWebSocket/index.en-US.md b/packages/hooks/src/useWebSocket/index.en-US.md
index 2bae50c3..f7e2bed3 100644
--- a/packages/hooks/src/useWebSocket/index.en-US.md
+++ b/packages/hooks/src/useWebSocket/index.en-US.md
@@ -10,6 +10,8 @@ group:
 
 # useWebSocket
 
+<Tag lang="en-US" tags="ssr&crossPlatform"></Tag>
+
 A hook for WebSocket.
 
 ## Examples
@@ -51,31 +53,31 @@ useWebSocket(socketUrl: string, options?: Options): Result;
 
 ### Params
 
-| Property | Description | Type | Default |
-|---------|----------------------------------------------|------------------------|--------|
-| socketUrl | Required, webSocket url | `string` | - |
-| options | Optional, connect the configuration item | `Options` | - |
+| Property  | Description                              | Type      | Default |
+|-----------|------------------------------------------|-----------|---------|
+| socketUrl | Required, webSocket url                  | `string`  | -       |
+| options   | Optional, connect the configuration item | `Options` | -       |
 
 
 #### Options
 
-| Options Property | Description | Type | Default |
-|---------|----------------------------------------------|------------------------|--------|
-| onOpen | Optional, the webSocket connection was successfully called back | `(event: WebSocketEventMap['open']) => void` | - |
-| onClose | Optional, webSocket close callback | `(event: WebSocketEventMap['close']) => void` | - |
-| onMessage | Optional, webSocket receive callback | `(message: WebSocketEventMap['message']) => void` | - |
-| onError | Optional, webSocket error callback | `(event: WebSocketEventMap['error']) => void` | - |
-| reconnectLimit | Optional, retry times | `number` | `3` |
-| reconnectInterval | Optional, retry the interval (ms) | `number` | `3000` |
-| manual | Optional, manually starts connection | `boolean` | `false` |
+| Options Property  | Description                                                     | Type                                              | Default |
+|-------------------|-----------------------------------------------------------------|---------------------------------------------------|---------|
+| onOpen            | Optional, the webSocket connection was successfully called back | `(event: WebSocketEventMap['open']) => void`      | -       |
+| onClose           | Optional, webSocket close callback                              | `(event: WebSocketEventMap['close']) => void`     | -       |
+| onMessage         | Optional, webSocket receive callback                            | `(message: WebSocketEventMap['message']) => void` | -       |
+| onError           | Optional, webSocket error callback                              | `(event: WebSocketEventMap['error']) => void`     | -       |
+| reconnectLimit    | Optional, retry times                                           | `number`                                          | `3`     |
+| reconnectInterval | Optional, retry the interval (ms)                               | `number`                                          | `3000`  |
+| manual            | Optional, manually starts connection                            | `boolean`                                         | `false` |
 
 ### Result
 
-| Options Property | Description | Type |
-| ------- | ---- | ------- |
-| latestMessage | latest message | `WebSocketEventMap['message']` |
-| sendMessage | Send message function | `WebSocket['send']` |
-| disconnect | Disconnect webSocket manually | `() => void` |
-| connect | Connect webSocket manually. If already connected, close the current one and connect. | `() => void` |
-| readyState | Current webSocket connection status | `ReadyState` |
-| webSocketIns | WebSocket instance | `WebSocket` |
+| Options Property | Description                                                                          | Type                           |
+|------------------|--------------------------------------------------------------------------------------|--------------------------------|
+| latestMessage    | Latest message                                                                       | `WebSocketEventMap['message']` |
+| sendMessage      | Send message function                                                                | `WebSocket['send']`            |
+| disconnect       | Disconnect webSocket manually                                                        | `() => void`                   |
+| connect          | Connect webSocket manually. If already connected, close the current one and connect. | `() => void`                   |
+| readyState       | Current webSocket connection status                                                  | `ReadyState`                   |
+| webSocketIns     | WebSocket instance                                                                   | `WebSocket`                    |
diff --git a/packages/hooks/src/useWebSocket/index.ts b/packages/hooks/src/useWebSocket/index.ts
index 9ee07705..9ae5b9d4 100644
--- a/packages/hooks/src/useWebSocket/index.ts
+++ b/packages/hooks/src/useWebSocket/index.ts
@@ -1,7 +1,7 @@
-import useUnmount from '../useUnmount';
-import useMemoizedFn from '../useMemoizedFn';
-
 import { useEffect, useRef, useState } from 'react';
+import useLatest from '../useLatest';
+import useMemoizedFn from '../useMemoizedFn';
+import useUnmount from '../useUnmount';
 
 export enum ReadyState {
   Connecting = 0,
@@ -40,6 +40,11 @@ export default function useWebSocket(socketUrl: string, options: Options = {}):
     onError,
   } = options;
 
+  const onOpenRef = useLatest(onOpen);
+  const onCloseRef = useLatest(onClose);
+  const onMessageRef = useLatest(onMessage);
+  const onErrorRef = useLatest(onError);
+
   const reconnectTimesRef = useRef(0);
   const reconnectTimerRef = useRef<NodeJS.Timeout>();
   const websocketRef = useRef<WebSocket>();
@@ -47,88 +52,79 @@ export default function useWebSocket(socketUrl: string, options: Options = {}):
   const [latestMessage, setLatestMessage] = useState<WebSocketEventMap['message']>();
   const [readyState, setReadyState] = useState<ReadyState>(ReadyState.Closed);
 
-  /**
-   * 重连
-   */
-  const reconnect = useMemoizedFn(() => {
+  const reconnect = () => {
     if (
       reconnectTimesRef.current < reconnectLimit &&
       websocketRef.current?.readyState !== ReadyState.Open
     ) {
-      reconnectTimerRef.current && clearTimeout(reconnectTimerRef.current);
+      if (reconnectTimerRef.current) {
+        clearTimeout(reconnectTimerRef.current);
+      }
 
       reconnectTimerRef.current = setTimeout(() => {
+        // eslint-disable-next-line @typescript-eslint/no-use-before-define
         connectWs();
         reconnectTimesRef.current++;
       }, reconnectInterval);
     }
-  });
+  };
 
-  const connectWs = useMemoizedFn(() => {
-    reconnectTimerRef.current && clearTimeout(reconnectTimerRef.current);
+  const connectWs = () => {
+    if (reconnectTimerRef.current) {
+      clearTimeout(reconnectTimerRef.current);
+    }
 
     if (websocketRef.current) {
       websocketRef.current.close();
     }
 
-    try {
-      websocketRef.current = new WebSocket(socketUrl);
-      websocketRef.current.onerror = (event) => {
-        reconnect();
-        onError && onError(event);
-        setReadyState(websocketRef.current?.readyState || ReadyState.Closed);
-      };
-      websocketRef.current.onopen = (event) => {
-        onOpen && onOpen(event);
-        reconnectTimesRef.current = 0;
-        setReadyState(websocketRef.current?.readyState || ReadyState.Closed);
-      };
-      websocketRef.current.onmessage = (message: WebSocketEventMap['message']) => {
-        onMessage && onMessage(message);
-        setLatestMessage(message);
-      };
-      websocketRef.current.onclose = (event) => {
-        reconnect();
-        onClose && onClose(event);
-        setReadyState(websocketRef.current?.readyState || ReadyState.Closed);
-      };
-    } catch (error) {
-      throw error;
-    }
-  });
+    websocketRef.current = new WebSocket(socketUrl);
+    setReadyState(ReadyState.Connecting);
+
+    websocketRef.current.onerror = (event) => {
+      reconnect();
+      onErrorRef.current?.(event);
+      setReadyState(websocketRef.current?.readyState || ReadyState.Closed);
+    };
+    websocketRef.current.onopen = (event) => {
+      onOpenRef.current?.(event);
+      reconnectTimesRef.current = 0;
+      setReadyState(websocketRef.current?.readyState || ReadyState.Closed);
+    };
+    websocketRef.current.onmessage = (message: WebSocketEventMap['message']) => {
+      onMessageRef.current?.(message);
+      setLatestMessage(message);
+    };
+    websocketRef.current.onclose = (event) => {
+      reconnect();
+      onCloseRef.current?.(event);
+      setReadyState(websocketRef.current?.readyState || ReadyState.Closed);
+    };
+  };
 
-  /**
-   * 发送消息
-   * @param message
-   */
-  const sendMessage: WebSocket['send'] = useMemoizedFn((message) => {
+  const sendMessage: WebSocket['send'] = (message) => {
     if (readyState === ReadyState.Open) {
       websocketRef.current?.send(message);
     } else {
       throw new Error('WebSocket disconnected');
     }
-  });
+  };
 
-  /**
-   * 手动 connect
-   */
-  const connect = useMemoizedFn(() => {
+  const connect = () => {
     reconnectTimesRef.current = 0;
     connectWs();
-  });
+  };
 
-  /**
-   * disconnect websocket
-   */
-  const disconnect = useMemoizedFn(() => {
-    reconnectTimerRef.current && clearTimeout(reconnectTimerRef.current);
+  const disconnect = () => {
+    if (reconnectTimerRef.current) {
+      clearTimeout(reconnectTimerRef.current);
+    }
 
     reconnectTimesRef.current = reconnectLimit;
     websocketRef.current?.close();
-  });
+  };
 
   useEffect(() => {
-    // 初始连接
     if (!manual) {
       connect();
     }
@@ -140,9 +136,9 @@ export default function useWebSocket(socketUrl: string, options: Options = {}):
 
   return {
     latestMessage,
-    sendMessage,
-    connect,
-    disconnect,
+    sendMessage: useMemoizedFn(sendMessage),
+    connect: useMemoizedFn(connect),
+    disconnect: useMemoizedFn(disconnect),
     readyState,
     webSocketIns: websocketRef.current,
   };
diff --git a/packages/hooks/src/useWebSocket/index.zh-CN.md b/packages/hooks/src/useWebSocket/index.zh-CN.md
index 813d2607..b968db0b 100644
--- a/packages/hooks/src/useWebSocket/index.zh-CN.md
+++ b/packages/hooks/src/useWebSocket/index.zh-CN.md
@@ -10,6 +10,8 @@ group:
 
 # useWebSocket
 
+<Tag lang="zh-CN" tags="ssr&crossPlatform"></Tag>
+
 用于处理 WebSocket 的 Hook。
 
 ## 代码演示
@@ -52,14 +54,14 @@ useWebSocket(socketUrl: string, options?: Options): Result;
 ### Params
 
 | 参数      | 说明                 | 类型      | 默认值 |
-| --------- | -------------------- | --------- | ------ |
+|-----------|----------------------|-----------|--------|
 | socketUrl | 必填，webSocket 地址 | `string`  | -      |
 | options   | 选填，连接配置项     | `Options` | -      |
 
 #### Options
 
 | 参数              | 说明                         | 类型                                              | 默认值  |
-| ----------------- | ---------------------------- | ------------------------------------------------- | ------- |
+|-------------------|------------------------------|---------------------------------------------------|---------|
 | onOpen            | 选填，webSocket 连接成功回调 | `(event: WebSocketEventMap['open']) => void`      | -       |
 | onClose           | 选填，webSocket 关闭回调     | `(event: WebSocketEventMap['close']) => void`     | -       |
 | onMessage         | 选填，webSocket 收到消息回调 | `(message: WebSocketEventMap['message']) => void` | -       |
@@ -70,11 +72,11 @@ useWebSocket(socketUrl: string, options?: Options): Result;
 
 ### Result
 
-| 参数          | 说明                    | 类型                           |
-| ------------- | ----------------------- | ------------------------------ |
-| latestMessage | 最新消息                | `WebSocketEventMap['message']` |
-| sendMessage   | 发送消息函数            | `WebSocket['send']`            |
-| disconnect    | 手动断开 webSocket 连接 | `() => void`                   |
-| connect       | 手动连接 webSocket，如果当前已有连接，则关闭后重新连接      | `() => void`                   |
-| readyState    | 当前 webSocket 连接状态 | `ReadyState`                   |
-| webSocketIns  | webSocket 实例          | `WebSocket`                    |
+| 参数          | 说明                                                   | 类型                           |
+|---------------|--------------------------------------------------------|--------------------------------|
+| latestMessage | 最新消息                                               | `WebSocketEventMap['message']` |
+| sendMessage   | 发送消息函数                                           | `WebSocket['send']`            |
+| disconnect    | 手动断开 webSocket 连接                                | `() => void`                   |
+| connect       | 手动连接 webSocket，如果当前已有连接，则关闭后重新连接 | `() => void`                   |
+| readyState    | 当前 webSocket 连接状态                                | `ReadyState`                   |
+| webSocketIns  | webSocket 实例                                         | `WebSocket`                    |


Test patch:
diff --git a/packages/hooks/src/useVirtualList/__tests__/index.test.ts b/packages/hooks/src/useVirtualList/__tests__/index.test.ts
index 5d797aa3..3262b05e 100644
--- a/packages/hooks/src/useVirtualList/__tests__/index.test.ts
+++ b/packages/hooks/src/useVirtualList/__tests__/index.test.ts
@@ -1,5 +1,5 @@
 import { act, renderHook, RenderHookResult } from '@testing-library/react-hooks';
-import useVirtualList, { OptionType } from '../index';
+import useVirtualList, { Options } from '../index';
 
 /* 暂时关闭 act 警告  见：https://github.com/testing-library/react-testing-library/issues/281#issuecomment-480349256 */
 const originalError = console.error;
@@ -24,7 +24,7 @@ describe('useVirtualList', () => {
   describe('virtual list render', () => {
     let mockRef = { scrollTop: 0, clientHeight: 300 };
     let hook: RenderHookResult<
-      { list: unknown[]; options: OptionType },
+      { list: unknown[]; options: Options },
       {
         list: unknown[];
         scrollTo: (index: number) => void;
@@ -42,7 +42,7 @@ describe('useVirtualList', () => {
 
     const setup = (list: any[] = [], options: {}) => {
       hook = renderHook(() =>
-        useVirtualList(list as unknown[], { itemHeight: 30, ...options } as OptionType),
+        useVirtualList(list as unknown[], { itemHeight: 30, ...options } as Options),
       );
       hook.result.current.containerProps.ref(mockRef);
     };
diff --git a/packages/hooks/src/useWebSocket/__tests__/index.test.ts b/packages/hooks/src/useWebSocket/__tests__/index.test.ts
index c430a44f..61bcefdc 100644
--- a/packages/hooks/src/useWebSocket/__tests__/index.test.ts
+++ b/packages/hooks/src/useWebSocket/__tests__/index.test.ts
@@ -1,8 +1,7 @@
-import { renderHook, act } from '@testing-library/react-hooks';
-import { waitFor } from '@testing-library/react';
+import { act, renderHook } from '@testing-library/react-hooks';
 import WS from 'jest-websocket-mock';
-import useWebSocket, { ReadyState } from '../index';
 import { sleep } from '../../utils/testingHelpers';
+import useWebSocket, { ReadyState } from '../index';
 
 const promise: Promise<void> = new Promise((resolve) => resolve());
 const wsUrl = 'ws://localhost:9999';
@@ -21,7 +20,7 @@ describe('useWebSocket', () => {
     const hooks = renderHook(() => useWebSocket(wsUrl));
 
     // connect
-    expect(hooks.result.current.readyState).toBe(ReadyState.Closed);
+    expect(hooks.result.current.readyState).toBe(ReadyState.Connecting);
     expect(hooks.result.current.latestMessage).toBe(undefined);
     await act(async () => {
       await wsServer.connected;