官方文檔 https://ant.design/components...
目錄
一、antd中的collapse
代碼目錄
1、組件結構圖(♦♦♦重要)
2、源碼節選:antd/components/collapse/collapse.tsx
3、源碼節選:antd/components/collapse/CollapsePanel.tsx
二、RcCollapse
代碼目錄
1、組件內部屬性結構及方法調用關係圖(♦♦♦重要)
2、組件應用的設計模式(♦♦♦重要)
3、源碼節選:rc-collapse/Collapse.jsx
4、源碼節選:rc-collapse/panel.jsx
一、antd中的collapse
antd組件中有些使用了React 底層基礎組件(查看具體列表點這裏),collapse就是這種類型的組件
antd中collapse主要源碼及組成結構如下,其中紅色標註的Rc開頭的組件是React底層基礎組件
代碼目錄
1、組件結構圖:
2、antd/components/collapse/collapse.tsx
export default class Collapse extends React.Component<CollapseProps, any> {
static Panel = CollapsePanel;
static defaultProps = {
prefixCls: 'ant-collapse',
bordered: true,
openAnimation: { ...animation, appear() { } },
};
renderExpandIcon = () => {
return (
<Icon type="right" className={`arrow`} />
);
}
render() {
const { prefixCls, className = '', bordered } = this.props;
const collapseClassName = classNames({
[`${prefixCls}-borderless`]: !bordered,
}, className);
return (
<RcCollapse
{...this.props}
className={collapseClassName}
expandIcon={this.renderExpandIcon}
/>
);
}
}
3、antd/components/collapse/CollapsePanel.tsx
export default class CollapsePanel extends React.Component<CollapsePanelProps, {}> {
render() {
const { prefixCls, className = '', showArrow = true } = this.props;
const collapsePanelClassName = classNames({
[`${prefixCls}-no-arrow`]: !showArrow,
}, className);
return <RcCollapse.Panel {...this.props} className={collapsePanelClassName} />;
}
}
二、RcCollapse
由上述Collapse源碼不難看出,摺疊面板組件的實現邏輯主要在RcCollapse中,下面是核心代碼、組件內部屬性結構及方法調用關係圖
代碼目錄
1、組件內部屬性結構及方法調用關係圖
2、組件應用的設計模式
這個組件中主要使用裏“聰明組件和傻瓜組件”模式、“組合組件”模式
a、聰明組件和傻瓜組件:
- 遵循職責分離原則,把獲取和管理數據的邏輯放在父組件,作爲聰明組件;把渲染界面的邏輯放在子組件,也就是傻瓜組件
-
聰明組件:Collapse,負責獲取和管理數據
getItems(),獲取數據,將props中的數據傳遞給子組件CollapsePanel; onClickItem(),管理數據,計算active的值傳遞給子組件;
-
傻瓜組件:Panel,只負責渲染;
根據父組件傳入的數據控制渲染邏輯,如active時的渲染效果
b、組合組件:
- 適用場景:
Collapse組件中Collapse是一個容器,包含一個或多個CollapsePanel,可以有一個(手風琴)或多個Panel展開(active),展開的樣式不同與未展開 - 一般實現:
每個Panel中傳入isActive狀態和onclick方法,在Panel內部實現渲染邏輯 - 缺陷:
每個Panel中要寫多個props參數
每個Panel中處理onclick的相同邏輯,重複代碼,增加Panel成本高
Collapse中控制active邏輯在每次新增Panel時也要修改 - 組合組件模式:
藉助React.Children.map或React.cloneElement使列表中多個子組件的公共處理移到父組件中統一處理 - Collapse中的實現:
Collapse渲染時調用this.getItems(),在this.getItems()中使用React.Children.map配置panel的onItemClick事件和activeKey等其他屬性
Panel只在點擊事件時調用父組件中定義的onItemClick,沒有冗餘代碼,降低了增加Panel的成本
PS:組件設計模式詳細內容可以自行查找相關資料,推薦掘金小冊《React 實戰:設計模式和最佳實踐》,本文部分內容摘自該文
3、rc-collapse/Collapse.jsx
class Collapse extends Component {
constructor(props) {
super(props);
……this.state = {
……
};
}
componentWillReceiveProps(nextProps) {
……
}
onClickItem(key) {
……
}
getItems() {
const activeKey = this.state.activeKey;
const { prefixCls, accordion, destroyInactivePanel, expandIcon } = this.props;
const newChildren = [];
Children.forEach(this.props.children, (child, index) => {
if (!child) return;
// If there is no key provide, use the panel order as default key
const key = child.key || String(index);
const { header, headerClass, disabled } = child.props;
let isActive = false;
if (accordion) {
isActive = activeKey[0] === key;
} else {
isActive = activeKey.indexOf(key) > -1;
}
const props = {
……
openAnimation: this.state.openAnimation,
accordion,
children: child.props.children,
onItemClick: disabled ? null : () => this.onClickItem(key),
expandIcon,
};
newChildren.push(React.cloneElement(child, props));
});
return newChildren;
}
setActiveKey(activeKey) {
if (!('activeKey' in this.props)) {
this.setState({ activeKey });
}
this.props.onChange(this.props.accordion ? activeKey[0] : activeKey);
}
render() {
const { prefixCls, className, style, accordion } = this.props;
const collapseClassName = classNames({
[prefixCls]: true,
[className]: !!className,
});
return (
<div className={collapseClassName} style={style} role={accordion ? 'tablist' : null}>
{this.getItems()}
</div>
);
}
}
4、rc-collapse/panel.jsx
class CollapsePanel extends Component {
handleItemClick = () => {
if (this.props.onItemClick) {
this.props.onItemClick();
}
}
handleKeyPress = (e) => {
if (e.key === 'Enter' || e.keyCode === 13 || e.which === 13) {
this.handleItemClick();
}
}
render() {
const {
……
} = this.props;
const headerCls = classNames(`${prefixCls}-header`, {
[headerClass]: headerClass,
});
const itemCls = classNames({
[`${prefixCls}-item`]: true,
[`${prefixCls}-item-active`]: isActive,
[`${prefixCls}-item-disabled`]: disabled,
}, className);
let icon = null;
if (showArrow && typeof expandIcon === 'function') {
icon = React.createElement(expandIcon, { ...this.props });
}
return (
<div className={itemCls} style={style} id={id}>
<div
className={headerCls}
onClick={this.handleItemClick}
role={accordion ? 'tab' : 'button'}
tabIndex={disabled ? -1 : 0}
aria-expanded={`${isActive}`}
onKeyPress={this.handleKeyPress}
>
{showArrow && (icon || <i className="arrow" />)}
{header}
</div>
<Animate
showProp="isActive"
exclusive
component=""
animation={this.props.openAnimation}
>
<PanelContent
prefixCls={prefixCls}
isActive={isActive}
destroyInactivePanel={destroyInactivePanel}
forceRender={forceRender}
role={accordion ? 'tabpanel' : null}
>
{children}
</PanelContent>
</Animate>
</div>
);
}
}