Repository: alibaba/hooks
Pull request #1783 (2022-07-27 09:57:42)

fix: refactor useMutation api
<!--
First of all, thank you for your contribution! 😄

New feature please send a pull request 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. ⚠️

- [ ] Doc is updated/provided or not needed
- [ ] Demo is updated/provided or not needed
- [ ] TypeScript definition is updated/provided or not needed
- [ ] Changelog is provided or not needed


CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/alibaba/hooks?pullRequest=1783) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/alibaba/hooks?pullRequest=1783) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/alibaba/hooks?pullRequest=1783) it.</sub>

Patch:
diff --git a/packages/hooks/src/useMutationObserver/demo/demo1.tsx b/packages/hooks/src/useMutationObserver/demo/demo1.tsx
index 7bbf1ee3..a157084b 100644
--- a/packages/hooks/src/useMutationObserver/demo/demo1.tsx
+++ b/packages/hooks/src/useMutationObserver/demo/demo1.tsx
@@ -13,18 +13,20 @@ const App: React.FC = () => {
 
   const ref = useRef<HTMLDivElement>(null);
 
-  const callback: MutationCallback = (mutationsList) => {
-    mutationsList.forEach(() => setCount(count + 1));
-  };
-
-  useMutationObserver(ref, callback, { attributes: true });
+  useMutationObserver(
+    (mutationsList) => {
+      mutationsList.forEach(() => setCount(count + 1));
+    },
+    ref,
+    { attributes: true },
+  );
 
   return (
     <div>
-      <div ref={ref} style={{ width, height: 30, border: '1px solid #000', marginBottom: 8 }}>
+      <div ref={ref} style={{ width, padding: 12, border: '1px solid #000', marginBottom: 8 }}>
         current width：{width}
       </div>
-      <button onClick={() => setWidth(width + 10)}>widening</button>
+      <button onClick={() => setWidth((w) => w + 10)}>widening</button>
       <p>Mutation count {count}</p>
     </div>
   );
diff --git a/packages/hooks/src/useMutationObserver/index.en-US.md b/packages/hooks/src/useMutationObserver/index.en-US.md
index d4ab89ae..18007a76 100644
--- a/packages/hooks/src/useMutationObserver/index.en-US.md
+++ b/packages/hooks/src/useMutationObserver/index.en-US.md
@@ -5,7 +5,7 @@ nav:
 
 # useMutationObserver
 
-A hook that provides the ability to watch for changes being made to the DOM tree
+A hook that provides the ability to watch for changes being made to the DOM tree, refer to [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
 
 ## Examples
 
@@ -17,40 +17,20 @@ A hook that provides the ability to watch for changes being made to the DOM tree
 
 ```typescript
 useMutationObserver(
-  target: Target,
   callback: MutationCallback,
+  target: Target,
   options?: MutationObserverInit,
 );
 ```
 
-### Params
+## Params
 
 | Property | Description           | Type                                                                | Default |
 | -------- | --------------------- | ------------------------------------------------------------------- | ------- |
-| target   | DOM element or ref    | `() => Element` \| `Element` \| `MutableRefObject<Element>`         |         |
-| callback | The callback function | `(mutations: MutationRecord[], observer: MutationObserver) => void` |         |
-| options  | Setting               | `MutationObserverInit`                                              |         |
+| target   | DOM element or ref    | `() => Element` \| `Element` \| `MutableRefObject<Element>`         | -       |
+| callback | The callback function | `(mutations: MutationRecord[], observer: MutationObserver) => void` | -       |
+| options  | Setting               | `MutationObserverInit`                                              | -       |
 
 ### Options
 
-| Property              | Description                                                                                                                          | Type       | Default |
-| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------- | ------- |
-| attributeFilter       | An array of attribute names to be monitored. If this property isn't included, changes to all attributes cause mutation notifications | `string[]` |         |
-| attributeOldValue     | Set to `true` to record the previous value of any attribute that changes when monitoring the node or nodes for attribute changes     | `boolean`  |         |
-| attributes            | Set to `true` to watch for changes to the value of attributes on the node or nodes being monitored                                   | `boolean`  | `false` |
-| characterData         | Set to `true` to monitor the specified target node for changes to the character data contained within the node or nodes              | `boolean`  |         |
-| characterDataOldValue | Set to `true` to record the previous value of a node's text whenever the text changes on nodes being monitored                       | `boolean`  |         |
-| childList             | Set to `true` to monitor the target node for the addition nodes or removal nodes (if subtree is true, its descendants)               | `boolean`  | `false` |
-| subtree               | Set to `true` to extend monitoring to the entire subtree of nodes rooted at target                                                   | `boolean`  | `false` |
-
-### notice
-
-Warning, Thrown in any of the following circumstances:
-
-<!-- https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe#exceptions -->
-
-- The options are configured such that nothing will actually be monitored. (For example, if childList, attributes, and characterData are all false.)
-
-- The value of attributes is false (indicating that attribute changes are not to be monitored), but attributeOldValue is true and/or attributeFilter is present.
-
-- The characterDataOldValue option is true but characterData is false (indicating that character changes are not to be monitored).
+For options, please refer to [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe#parameters)
diff --git a/packages/hooks/src/useMutationObserver/index.ts b/packages/hooks/src/useMutationObserver/index.ts
index d5539ebb..14a2444e 100644
--- a/packages/hooks/src/useMutationObserver/index.ts
+++ b/packages/hooks/src/useMutationObserver/index.ts
@@ -1,19 +1,22 @@
 import { getTargetElement } from '../utils/domTarget';
 import type { BasicTarget } from '../utils/domTarget';
 import useDeepCompareEffectWithTarget from '../utils/useDeepCompareWithTarget';
+import useLatest from '../useLatest';
 
 const useMutationObserver = (
-  target: BasicTarget,
   callback: MutationCallback,
+  target: BasicTarget,
   options: MutationObserverInit = {},
 ): void => {
+  const callbackRef = useLatest(callback);
+
   useDeepCompareEffectWithTarget(
     () => {
       const element = getTargetElement(target);
       if (!element) {
         return;
       }
-      const observer = new MutationObserver(callback);
+      const observer = new MutationObserver(callbackRef.current);
       observer.observe(element, options);
       return () => {
         if (observer) {
@@ -21,7 +24,7 @@ const useMutationObserver = (
         }
       };
     },
-    [options, callback],
+    [options],
     target,
   );
 };
diff --git a/packages/hooks/src/useMutationObserver/index.zh-CN.md b/packages/hooks/src/useMutationObserver/index.zh-CN.md
index 47aa6936..b4ab47fa 100644
--- a/packages/hooks/src/useMutationObserver/index.zh-CN.md
+++ b/packages/hooks/src/useMutationObserver/index.zh-CN.md
@@ -5,7 +5,7 @@ nav:
 
 # useMutationObserver
 
-一个监听指定的 DOM 发生变化的 hook
+一个监听指定的 DOM 树发生变化的 Hook，参考 [MutationObserver](https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver)
 
 ## 代码演示
 
@@ -17,8 +17,8 @@ nav:
 
 ```typescript
 useMutationObserver(
-  target: Target,
   callback: MutationCallback,
+  target: Target,
   options?: MutationObserverInit,
 );
 ```
@@ -27,30 +27,10 @@ useMutationObserver(
 
 | 参数     | 说明             | 类型                                                                | 默认值 |
 | -------- | ---------------- | ------------------------------------------------------------------- | ------ |
-| target   | DOM 节点或者 Ref | `Element` \| `() => Element` \| `MutableRefObject<Element>`         |        |
-| callback | 触发的回调函数   | `(mutations: MutationRecord[], observer: MutationObserver) => void` |        |
-| options  | 设置项           | `MutationObserverInit`                                              |        |
+| callback | 触发的回调函数   | `(mutations: MutationRecord[], observer: MutationObserver) => void` | -      |
+| target   | DOM 节点或者 Ref | `Element` \| `() => Element` \| `MutableRefObject<Element>`         | -      |
+| options  | 设置项           | `MutationObserverInit`                                              | `{}`   |
 
 ### Options
 
-| 参数                  | 说明                                                                                     | 类型       | 默认值  |
-| --------------------- | ---------------------------------------------------------------------------------------- | ---------- | ------- |
-| attributeFilter       | 要监视的特定属性名称的数组。如果未包含此属性，则对所有属性的更改都会触发变动通知         | `string[]` |         |
-| attributeOldValue     | 当监视节点的属性改动时，将此属性设为 `true` 将记录任何有改动的属性的上一个值             | `boolean`  |         |
-| attributes            | 设为 `true` 以观察受监视元素的属性值变更                                                 | `boolean`  | `false` |
-| characterData         | 设为 `true` 以监视指定目标节点或子节点树中节点所包含的字符数据的变化                     | `boolean`  |         |
-| characterDataOldValue | 设为 `true` 以在文本在受监视节点上发生更改时记录节点文本的先前值                         | `boolean`  |         |
-| childList             | 设为 `true` 以监视目标节点添加或删除新的子节点（如果 subtree 为 true，则也包含子孙节点） | `boolean`  | `false` |
-| subtree               | 设为 `true` 以将监视范围扩展至目标节点整个节点树中的所有节点                             | `boolean`  | `false` |
-
-### notice
-
-注意，以下任一情况都会抛出异常：
-
-<!-- https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver/observe#%E5%BC%82%E5%B8%B8 -->
-
-- 在 `options` 配置项对象中，必须至少将 `attributes`、`characterData` 或 `childList` 中的一个属性设置为 `true`，否则会报错
-
-- `attributes` 选项为 `false`（表示不监视属性更改），但是 `attributeOldValue` 选项为 `true` 并且/或者 `attributeFilter` 配置存在
-
-- `characterDataOldValue` 选项为 `true`，但是 `characterData` 选项为 `false`（表示不跟踪字符更改）
+配置项请参考 [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe#parameters)


Test patch:
diff --git a/packages/hooks/src/useMutationObserver/__tests__/index.test.ts b/packages/hooks/src/useMutationObserver/__tests__/index.test.ts
index 9f660535..b4ee9ce7 100644
--- a/packages/hooks/src/useMutationObserver/__tests__/index.test.ts
+++ b/packages/hooks/src/useMutationObserver/__tests__/index.test.ts
@@ -17,7 +17,7 @@ describe('useMutationObserver', () => {
 
   it('should callback work when target style be changed', async () => {
     const callback = jest.fn();
-    const { rerender } = renderHook(() => useMutationObserver(() => container, callback, options));
+    const { rerender } = renderHook(() => useMutationObserver(callback, () => container, options));
     container.style.backgroundColor = '#000';
     await rerender();
     expect(callback).toBeCalled();
@@ -25,7 +25,7 @@ describe('useMutationObserver', () => {
 
   it('should callback work when target node tree be changed', async () => {
     const callback = jest.fn();
-    const { rerender } = renderHook(() => useMutationObserver(() => container, callback, options));
+    const { rerender } = renderHook(() => useMutationObserver(callback, () => container, options));
     const paraEl = document.createElement('p');
     container.appendChild(paraEl);
     await rerender();
@@ -34,7 +34,7 @@ describe('useMutationObserver', () => {
 
   it('should not work when target is null', async () => {
     const callback = jest.fn();
-    const { rerender } = renderHook(() => useMutationObserver(null, callback, options));
+    const { rerender } = renderHook(() => useMutationObserver(callback, null, options));
     container.style.backgroundColor = '#000';
     await rerender();
     expect(callback).not.toBeCalled();