官方文檔 https://ant.design/components/collapse-cn/
目錄
一、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> ); } }
