Skip to content

Commit

Permalink
fix: warning for repeatedly calling createRoot in react18 of masking …
Browse files Browse the repository at this point in the history
…related components (#231)

* chore: add arco-mobile eslint plugin
  • Loading branch information
TinaPeach authored Feb 22, 2024
1 parent ae4f39b commit 6879ad7
Show file tree
Hide file tree
Showing 14 changed files with 495 additions and 29 deletions.
5 changes: 3 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"airbnb",
"plugin:react/recommended",
"plugin:prettier/recommended",
"plugin:react-hooks/recommended"
"plugin:react-hooks/recommended",
"plugin:arco-mobile/recommended"
],
"parserOptions": {
"ecmaFeatures": {
Expand All @@ -21,7 +22,7 @@
},
"sourceType": "module"
},
"plugins": ["import", "react", "babel", "@typescript-eslint/eslint-plugin"],
"plugins": ["import", "react", "babel", "@typescript-eslint/eslint-plugin", "arco-mobile"],
"globals": {
"ActiveXObject": false,
"describe": false,
Expand Down
422 changes: 414 additions & 8 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"eslint": "^7.32.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-arco-mobile": "^1.0.1",
"eslint-plugin-babel": "^5.3.1",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jsx-a11y": "^6.5.1",
Expand Down
21 changes: 21 additions & 0 deletions packages/arcodesign/components/_helpers/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React, { FunctionComponent } from 'react';
import { RootType, render as copyRender } from './react-dom';
import { GlobalContextParams } from '../context-provider';

export const renderRootCache: Record<string, RootType | undefined> = {};

export class ReactDOMRender {
root: RootType | undefined;

Expand All @@ -11,14 +13,20 @@ export class ReactDOMRender {

context: GlobalContextParams | undefined;

rootCacheId: string | undefined;

constructor(
app: FunctionComponent,
container: Element | DocumentFragment,
context?: GlobalContextParams,
rootCacheId?: string, // root id in cache
root?: RootType, // use root in cache
) {
this.app = app;
this.container = container;
this.context = context;
this.rootCacheId = rootCacheId;
this.root = root;
}

render = props => {
Expand All @@ -31,8 +39,21 @@ export class ReactDOMRender {
}
};

setRootCache = () => {
if (this.rootCacheId) {
renderRootCache[this.rootCacheId] = this.root;
}
};

clearRootCache = () => {
if (this.rootCacheId) {
delete renderRootCache[this.rootCacheId];
}
};

unmount = () => {
this.root?._unmount();
this.root = undefined;
this.clearRootCache();
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('Dialog', () => {
jest.useRealTimers();
});

testMaskingCase('dialog', Dialog, prefix, `${prefix}-container`);
testMaskingCase('dialog', Dialog, prefix, 'ARCO_DIALOG', `${prefix}-container`);

it('should support `footer` to customize operation buttons', async () => {
const onOk = jest.fn();
Expand Down
6 changes: 1 addition & 5 deletions packages/arcodesign/components/image-preview/methods.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export function open<P extends OpenBaseProps>(Component: React.FunctionComponent
const baseProps: Config & {
// 从config继承的属性
// @en Properties inherited from config
unmountOnExit?: boolean;
getContainer?: () => HTMLElement;
key?: string;
openIndex: P['openIndex'];
Expand All @@ -26,7 +25,6 @@ export function open<P extends OpenBaseProps>(Component: React.FunctionComponent
onClose?: P['onClose'];
close: P['close'];
} = {
unmountOnExit: true,
...(config || {}),
close: () => {},
};
Expand Down Expand Up @@ -56,9 +54,7 @@ export function open<P extends OpenBaseProps>(Component: React.FunctionComponent
dynamicProps.close = close;
dynamicProps.onClose = () => {
baseProps.onClose && baseProps.onClose();
if (baseProps.unmountOnExit) {
removeElement(div);
}
removeElement(div);
};
dynamicProps.openIndex = -1;
render(dynamicProps);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ describe('Masking', () => {
jest.useRealTimers();
});

testMaskingCase('masking', Masking, prefix);
testMaskingCase('masking', Masking, prefix, 'ARCO_MASKING');
});
30 changes: 22 additions & 8 deletions packages/arcodesign/components/masking/__test__/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import userEvent from '@testing-library/user-event';
import { defaultContext } from '../../context-provider';
import Button from '../../button';
import '@testing-library/jest-dom';
import { pureDelay } from '../../../tests/helpers/utils';

const maskingPrefix = `${defaultContext.prefixCls}-masking`;

Expand Down Expand Up @@ -39,6 +40,7 @@ export function testMaskingCase(
compName,
Comp,
prefix,
containerId,
contentClass = `${prefix}-content`,
maskClass = `${prefix}-mask`,
) {
Expand Down Expand Up @@ -97,23 +99,35 @@ export function testMaskingCase(
it('should support `open`', () => {
const onClose = jest.fn();
const ref = React.createRef();
window.maskingInstance = Comp.open({
const props = {
ref,
onClose,
maskTransitionTimeout: 1000,
className: 'demo-global',
children: 'Content',
};
const divId = `#_${containerId}_DIV__`;
// keep div after close when unmountOnExit=false
window.maskingInstance = Comp.open({
...props,
unmountOnExit: false,
});
act(() => {
jest.advanceTimersByTime(1100);
});
pureDelay(1100);
expect(document.querySelectorAll(divId)).toHaveLength(1);
window.maskingInstance.close();
pureDelay(1100);
expect(onClose.mock.calls).toHaveLength(1);
expect(document.querySelectorAll(divId)).toHaveLength(1);
expect(document.querySelectorAll('.demo-global')).toHaveLength(1);
// open again without unmountOnExit=false to clear side effects
window.maskingInstance = Comp.open(props);
pureDelay(1100);
expect(document.querySelectorAll('.demo-global')).toHaveLength(1);
expect(document.querySelectorAll(divId)).toHaveLength(1);
expect(typeof window.maskingInstance.close).toBe('function');
expect(typeof window.maskingInstance.update).toBe('function');
window.maskingInstance.close();
act(() => {
jest.advanceTimersByTime(1100);
});
expect(onClose.mock.calls).toHaveLength(1);
pureDelay(1100);
expect(document.querySelectorAll(divId)).toHaveLength(0);
});
}
17 changes: 14 additions & 3 deletions packages/arcodesign/components/masking/methods.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { appendElementById, removeElement, nextTick } from '@arco-design/mobile-utils';
import { ReactDOMRender } from '../_helpers/render';
import { ReactDOMRender, renderRootCache } from '../_helpers/render';
import { GlobalContextParams } from '../context-provider';

export interface OpenBaseProps {
Expand Down Expand Up @@ -28,9 +28,16 @@ export function getOpenMethod<T extends { key?: string }, P extends OpenBaseProp

// 不同的key用不同的容器挂载
const id = `_${containerId || 'ARCO_MASKING'}_DIV_${config.key || ''}_`;
const { child: div } = appendElementById(id, baseProps.getContainer);
const existedDiv = baseProps.unmountOnExit ? null : document.getElementById(id);
const div = existedDiv || appendElementById(id, baseProps.getContainer).child;
let leaving = false;
const { render } = new ReactDOMRender(Component, div, context);
const { render, unmount, setRootCache } = new ReactDOMRender(
Component,
div,
context,
id,
existedDiv ? renderRootCache[id] : undefined,
);
let dynamicProps = { ...baseProps, getContainer: () => div };

function update(newConfig: T) {
Expand All @@ -51,10 +58,14 @@ export function getOpenMethod<T extends { key?: string }, P extends OpenBaseProp
dynamicProps.onClose = scene => {
baseProps.onClose && baseProps.onClose(scene);
if (baseProps.unmountOnExit) {
unmount();
removeElement(div);
}
};
render(dynamicProps);
if (!baseProps.unmountOnExit) {
setRootCache();
}
nextTick(() => {
if (leaving) return;
dynamicProps.visible = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe('PopupSwiper', () => {
'popup-swiper',
PopupSwiper,
prefix,
'ARCO_POPUP_SWIPER',
`${popupPrefix}-content`,
`${popupPrefix}-mask`,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ describe('Popup', () => {
jest.useRealTimers();
});

testMaskingCase('popup', Popup, prefix);
testMaskingCase('popup', Popup, prefix, 'ARCO_POPUP');
});
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ Use scroll events to monitor whether children enter or leave the viewport.
|----------|-------------|------|
|dom|The outermost element DOM|HTMLDivElement|
|checkVisible|Ignore the state of the element before and after, manually check whether the element is in the viewport, trigger the onVisibleChange callback function|() =\> boolean|
|flushVisibleStatus|Reset the initial visible state of the element to false, and re\-detect the visibility of the element, the priority is lower than 'disabled'(Usually used to re\-listen when elements inside ShowMonitor change)|() =\> void|
1 change: 1 addition & 0 deletions packages/arcodesign/components/show-monitor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@
|----------|-------------|------|
|dom|最外层元素 DOM|HTMLDivElement|
|checkVisible|忽略元素前后状态,手动检查元素是否在视口内,触发onVisibleChange回调函数|() =\> boolean|
|flushVisibleStatus|重置元素初始可见态为false,并重新对元素可见度发起检测,优先级低于disabled(通常用在对ShowMonitor内部元素变化时发起的重新监听)|() =\> void|
13 changes: 13 additions & 0 deletions packages/arcodesign/components/show-monitor/__ast__/index.ast.json
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,19 @@
"en": "Ignore the state of the element before and after, manually check whether the element is in the viewport, trigger the onVisibleChange callback function"
},
"descWithTags": "忽略元素前后状态,手动检查元素是否在视口内,触发onVisibleChange回调函数"
},
"flushVisibleStatus": {
"name": "flushVisibleStatus",
"required": true,
"description": "重置元素初始可见态为false,并重新对元素可见度发起检测,优先级低于disabled(通常用在对ShowMonitor内部元素变化时发起的重新监听)\n@en Reset the initial visible state of the element to false, and re-detect the visibility of the element, the priority is lower than 'disabled'(Usually used to re-listen when elements inside ShowMonitor change)",
"defaultValue": null,
"type": {
"name": "() => void"
},
"tags": {
"en": "Reset the initial visible state of the element to false, and re-detect the visibility of the element, the priority is lower than 'disabled'(Usually used to re-listen when elements inside ShowMonitor change)"
},
"descWithTags": "重置元素初始可见态为false,并重新对元素可见度发起检测,优先级低于disabled(通常用在对ShowMonitor内部元素变化时发起的重新监听)"
}
}
},
Expand Down

0 comments on commit 6879ad7

Please sign in to comment.