antd源碼分析之——摺疊面板(collapse)

官方文檔 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底層基礎組件

代碼目錄

clipboard.png

1、組件結構圖:

clipboard.png

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中,下面是核心代碼、組件內部屬性結構及方法調用關係圖

代碼目錄

clipboard.png

1、組件內部屬性結構及方法調用關係圖

clipboard.png

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>
    );
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章