Repository: alibaba/hooks
Pull request #766 (2020-12-04 04:15:18)

feat: useUnmountedRef
使用hooks实现获取当前组件是否已经卸载的状态，避免因为组件卸载后修改状态而造成的内存泄漏

github-actions[bot]: 
⚡️ Deploying PR Preview 98c25695e77035e52f8cda45464477fef47dc6aa to [surge.sh](https://alibaba-hooks-preview-pr-766.surge.sh) ... [Build logs](https://github.com/alibaba/hooks/runs/1496815179)

<a href="https://github.com/alibaba/hooks/runs/1496815179"><img width="300" src="https://user-images.githubusercontent.com/507615/90240294-8d2abd00-de5b-11ea-8140-4840a0b2d571.gif"></a>

<sub>🤖 By [surge-preview](https://github.com/afc163/surge-preview)</sub>
  
<!-- Sticky Pull Request Comment: Surge Preview preview' -->

Patch:
diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts
index 2cde259d..f07bf38d 100644
--- a/packages/hooks/src/index.ts
+++ b/packages/hooks/src/index.ts
@@ -56,6 +56,7 @@ import useCountDown from './useCountDown';
 import useWebSocket from './useWebSocket';
 import useLockFn from './useLockFn';
 import useTrackedEffect from './useTrackedEffect';
+import useUnmountedRef from './useUnmountedRef';
 
 const useControlledValue: typeof useControllableValue = function (...args) {
   console.warn(
@@ -126,4 +127,5 @@ export {
   useTrackedEffect,
   useWebSocket,
   useLockFn,
+  useUnmountedRef,
 };
diff --git a/packages/hooks/src/useUnmountedRef/demo/demo1.tsx b/packages/hooks/src/useUnmountedRef/demo/demo1.tsx
new file mode 100644
index 00000000..c77982ad
--- /dev/null
+++ b/packages/hooks/src/useUnmountedRef/demo/demo1.tsx
@@ -0,0 +1,31 @@
+/**
+ * title: Default usage
+ * desc: The function is called when the component is first rendered, and the returned object has a property named 'current' which indicates whether the current component has been unmounted
+ *
+ * title.zh-CN: 基础用法
+ * desc.zh-CN: 在组件首次渲染时执行方法，返回一个含有current属性的ref对象，current表示当前组件是否已被卸载
+ */
+
+import React, { useState } from 'react';
+import { useUnmountedRef } from 'ahooks';
+
+const Button = ({ setVisible }) => {
+  const unmountRef: { current: boolean } = useUnmountedRef();
+  const [text, setText] = useState('I am mounted');
+  const handleClick = async () => {
+    await setVisible();
+    !unmountRef.current && setText('I am unmounted');
+  };
+  return <button onClick={handleClick}>{text}</button>;
+};
+
+export default () => {
+  const [visible, setVisible] = useState(true);
+  return (
+    <div>
+      click the button to unmount it
+      <br />
+      {visible ? <Button setVisible={() => setVisible(false)} /> : 'nothing'}
+    </div>
+  );
+};
diff --git a/packages/hooks/src/useUnmountedRef/index.en-US.md b/packages/hooks/src/useUnmountedRef/index.en-US.md
new file mode 100644
index 00000000..be9cb329
--- /dev/null
+++ b/packages/hooks/src/useUnmountedRef/index.en-US.md
@@ -0,0 +1,31 @@
+---
+title: useUnmountedRef
+nav:
+  title: Hooks
+  path: /hooks
+group:
+  title: LifeCycle
+  path: /life-cycle
+---
+
+# useUnmountedRef
+
+a hook of whether the current component has been unmounted, used to avoid memory leaks caused by updating the state after the component has been unmounted
+
+## Examples
+
+### Default Usage
+
+<code src="./demo/demo1.tsx" />
+
+## API
+
+```typescript
+const unmountRef: { current: boolean } = useUnmountedRef;
+```
+
+### Result
+
+| Property | Description                      | Type         |
+|----------|----------------------------------|--------------|
+| unmountRef | the object's property 'current' can return whether the current component has been unmounted | `{ current: boolean }` |
diff --git a/packages/hooks/src/useUnmountedRef/index.ts b/packages/hooks/src/useUnmountedRef/index.ts
new file mode 100644
index 00000000..6a00af71
--- /dev/null
+++ b/packages/hooks/src/useUnmountedRef/index.ts
@@ -0,0 +1,13 @@
+import { useRef, useEffect } from 'react';
+
+const useUnmountedRef = () => {
+  const unmountedRef = useRef(false);
+  useEffect(() => {
+    return () => {
+      unmountedRef.current = true;
+    };
+  }, []);
+  return unmountedRef;
+};
+
+export default useUnmountedRef;
diff --git a/packages/hooks/src/useUnmountedRef/index.zh-CN.md b/packages/hooks/src/useUnmountedRef/index.zh-CN.md
new file mode 100644
index 00000000..4838e25a
--- /dev/null
+++ b/packages/hooks/src/useUnmountedRef/index.zh-CN.md
@@ -0,0 +1,31 @@
+---
+title: useUnmountedRef
+nav:
+  title: Hooks
+  path: /hooks
+group:
+  title: LifeCycle
+  path: /life-cycle
+---
+
+# useUnmountedRef
+
+获取当前组件是否已经卸载的 hook，用于避免因组件卸载后更新状态而导致的内存泄漏
+
+## 代码演示
+
+### 基础用法
+
+<code src="./demo/demo1.tsx" />
+
+## API
+
+```typescript
+const unmountRef: { current: boolean } = useUnmountedRef;
+```
+
+### Result
+
+| 参数 | 说明               | 类型         |
+|------|--------------------|--------------|
+| unmountRef | 对象的current属性可返回当前组件是否已经卸载 | `{ current: boolean }` |


Test patch:
diff --git a/packages/hooks/src/useUnmountedRef/__tests__/index.test.ts b/packages/hooks/src/useUnmountedRef/__tests__/index.test.ts
new file mode 100644
index 00000000..c0beeec9
--- /dev/null
+++ b/packages/hooks/src/useUnmountedRef/__tests__/index.test.ts
@@ -0,0 +1,17 @@
+import { renderHook } from '@testing-library/react-hooks';
+import useUnmountedRef from '../index';
+
+describe('useMountStatus', () => {
+  it('should be defined', () => {
+    expect(useUnmountedRef).toBeDefined();
+  });
+  it('test mount', async () => {
+    const hook = renderHook(() => useUnmountedRef());
+    expect(hook.result.current.current).toBe(false);
+  });
+  it('test unmounted', async () => {
+    const hook = renderHook(() => useUnmountedRef());
+    hook.unmount();
+    expect(hook.result.current.current).toBe(true);
+  });
+});