Skip to content

Commit f4ee71f

Browse files
authored
refactor: Collapse to function component (#287)
* chore: add typings field * refactor: Collapse to function component * fix: fix TS type error * chore: replace `onKeyPress` with `onKeydown` * test: update case * Revert "test: update case" This reverts commit 3ff13c6. * Revert "chore: replace `onKeyPress` with `onKeydown`" This reverts commit 55e1fa4. * update scripts * test: add case
1 parent 4abce43 commit f4ee71f

File tree

5 files changed

+229
-279
lines changed

5 files changed

+229
-279
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"license": "MIT",
2222
"main": "./lib/index",
2323
"module": "./es/index",
24+
"typings": "es/index.d.ts",
2425
"files": [
2526
"lib",
2627
"es",
@@ -29,7 +30,7 @@
2930
"scripts": {
3031
"build": "dumi build",
3132
"compile": "father build && lessc assets/index.less assets/index.css",
32-
"coverage": "father test --coverage",
33+
"coverage": "npm test -- --coverage",
3334
"lint": "eslint src/ --ext .ts,.tsx,.jsx,.js,.md",
3435
"prepublishOnly": "npm run compile && np --yolo --no-publish",
3536
"prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",

src/Collapse.tsx

Lines changed: 56 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import React from 'react';
12
import classNames from 'classnames';
23
import toArray from 'rc-util/lib/Children/toArray';
3-
import * as React from 'react';
4-
import isEqual from 'rc-util/lib/isEqual';
5-
import type { CollapseProps, CollapsibleType } from './interface';
4+
import useMergedState from 'rc-util/lib/hooks/useMergedState';
65
import CollapsePanel from './Panel';
6+
import type { CollapseProps, CollapsibleType } from './interface';
77

88
function getActiveKeysArray(activeKey: React.Key | React.Key[]) {
99
let currentActiveKey = activeKey;
@@ -15,84 +15,59 @@ function getActiveKeysArray(activeKey: React.Key | React.Key[]) {
1515
return currentActiveKey.map((key) => String(key));
1616
}
1717

18-
export interface CollapseState {
19-
activeKey: React.Key[];
20-
}
21-
22-
class Collapse extends React.Component<CollapseProps, CollapseState> {
23-
static defaultProps = {
24-
prefixCls: 'rc-collapse',
25-
onChange() {},
26-
accordion: false,
27-
destroyInactivePanel: false,
28-
};
29-
30-
static Panel = CollapsePanel;
31-
32-
constructor(props: CollapseProps) {
33-
super(props);
34-
35-
const { activeKey, defaultActiveKey } = props;
36-
let currentActiveKey = defaultActiveKey;
37-
if ('activeKey' in props) {
38-
currentActiveKey = activeKey;
39-
}
40-
41-
this.state = {
42-
activeKey: getActiveKeysArray(currentActiveKey),
43-
};
44-
}
45-
46-
shouldComponentUpdate(nextProps: CollapseProps, nextState: CollapseState) {
47-
return !isEqual(this.props, nextProps, true) || !isEqual(this.state, nextState, true);
48-
}
18+
function Collapse(props: CollapseProps) {
19+
const {
20+
prefixCls = 'rc-collapse',
21+
destroyInactivePanel = false,
22+
style,
23+
accordion,
24+
className,
25+
children: rawChildren,
26+
collapsible,
27+
openMotion,
28+
expandIcon,
29+
activeKey: rawActiveKey,
30+
defaultActiveKey,
31+
onChange,
32+
} = props;
33+
34+
const collapseClassName = classNames(prefixCls, className);
35+
36+
const [activeKey, setActiveKey] = useMergedState<React.Key | React.Key[], React.Key[]>([], {
37+
value: rawActiveKey,
38+
onChange: (v) => onChange?.(v),
39+
defaultValue: defaultActiveKey,
40+
postState: getActiveKeysArray,
41+
});
42+
43+
const onClickItem = (key: React.Key) =>
44+
setActiveKey(() => {
45+
if (accordion) {
46+
return activeKey[0] === key ? [] : [key];
47+
}
4948

50-
onClickItem = (key: React.Key) => {
51-
let { activeKey } = this.state;
52-
if (this.props.accordion) {
53-
activeKey = activeKey[0] === key ? [] : [key];
54-
} else {
55-
activeKey = [...activeKey];
5649
const index = activeKey.indexOf(key);
5750
const isActive = index > -1;
5851
if (isActive) {
59-
// remove active state
60-
activeKey.splice(index, 1);
61-
} else {
62-
activeKey.push(key);
52+
return activeKey.filter((item) => item !== key);
6353
}
64-
}
65-
this.setActiveKey(activeKey);
66-
};
6754

68-
static getDerivedStateFromProps(nextProps: CollapseProps) {
69-
const newState: Partial<CollapseState> = {};
70-
if ('activeKey' in nextProps) {
71-
newState.activeKey = getActiveKeysArray(nextProps.activeKey);
72-
}
73-
return newState;
74-
}
55+
return [...activeKey, key];
56+
});
7557

76-
getNewChild = (child: React.ReactElement, index: number) => {
58+
// ======================== Children ========================
59+
const getNewChild = (child: React.ReactElement, index: number) => {
7760
if (!child) return null;
7861

79-
const { activeKey } = this.state;
80-
const {
81-
prefixCls,
82-
openMotion,
83-
accordion,
84-
destroyInactivePanel: rootDestroyInactivePanel,
85-
expandIcon,
86-
collapsible,
87-
} = this.props;
88-
// If there is no key provide, use the panel order as default key
8962
const key = child.key || String(index);
63+
9064
const {
9165
header,
9266
headerClass,
93-
destroyInactivePanel,
67+
destroyInactivePanel: childDestroyInactivePanel,
9468
collapsible: childCollapsible,
9569
} = child.props;
70+
9671
let isActive = false;
9772
if (accordion) {
9873
isActive = activeKey[0] === key;
@@ -102,18 +77,18 @@ class Collapse extends React.Component<CollapseProps, CollapseState> {
10277

10378
const mergeCollapsible: CollapsibleType = childCollapsible ?? collapsible;
10479

105-
const props = {
80+
const childProps = {
10681
key,
10782
panelKey: key,
10883
header,
10984
headerClass,
11085
isActive,
11186
prefixCls,
112-
destroyInactivePanel: destroyInactivePanel ?? rootDestroyInactivePanel,
87+
destroyInactivePanel: childDestroyInactivePanel ?? destroyInactivePanel,
11388
openMotion,
11489
accordion,
11590
children: child.props.children,
116-
onItemClick: mergeCollapsible === 'disabled' ? null : this.onClickItem,
91+
onItemClick: mergeCollapsible === 'disabled' ? null : onClickItem,
11792
expandIcon,
11893
collapsible: mergeCollapsible,
11994
};
@@ -123,39 +98,23 @@ class Collapse extends React.Component<CollapseProps, CollapseState> {
12398
return child;
12499
}
125100

126-
Object.keys(props).forEach((propName: keyof typeof props) => {
127-
if (typeof props[propName] === 'undefined') {
128-
delete props[propName];
101+
Object.keys(childProps).forEach((propName) => {
102+
if (typeof childProps[propName] === 'undefined') {
103+
delete childProps[propName];
129104
}
130105
});
131106

132-
return React.cloneElement(child, props);
133-
};
134-
135-
getItems = () => {
136-
const { children } = this.props;
137-
return toArray(children).map(this.getNewChild);
107+
return React.cloneElement(child, childProps);
138108
};
139109

140-
setActiveKey = (activeKey: React.Key[]) => {
141-
if (!('activeKey' in this.props)) {
142-
this.setState({ activeKey });
143-
}
144-
this.props.onChange(this.props.accordion ? activeKey[0] : activeKey);
145-
};
110+
const children = toArray(rawChildren).map(getNewChild);
146111

147-
render() {
148-
const { prefixCls, className, style, accordion } = this.props;
149-
const collapseClassName = classNames({
150-
[prefixCls]: true,
151-
[className]: !!className,
152-
});
153-
return (
154-
<div className={collapseClassName} style={style} role={accordion ? 'tablist' : null}>
155-
{this.getItems()}
156-
</div>
157-
);
158-
}
112+
// ======================== Render ========================
113+
return (
114+
<div className={collapseClassName} style={style} role={accordion ? 'tablist' : undefined}>
115+
{children}
116+
</div>
117+
);
159118
}
160119

161-
export default Collapse;
120+
export default Object.assign(Collapse, { Panel: CollapsePanel });

0 commit comments

Comments
 (0)