antd原始碼解讀(8)- Dropdown

markzzw發表於2017-10-25

DropDown 下拉選單

下拉選單元件是一個可以將頁面上比較冗雜的操作收納在一個點,以便節省頁面空間,達到整潔美觀的目的。

Antd的下拉選單元件中有一個點,就是他的內部元素必須是Antd的Menu元件,感覺有點捆綁的意思。

這裡留一個小問題,為什麼觸發方式是一個陣列,而不是單個的

  export interface DropDownProps {
    trigger?: ('click' | 'hover')[]; // 觸發方式
    overlay: React.ReactNode; // 下拉選單所承載的內容元素,要求為Antd的Menu元件
    style?: React.CSSProperties; // 行內樣式
    onVisibleChange?: (visible?: boolean) => void; // 監聽下拉選單出現/消失
    visible?: boolean; // 選單是否顯示
    disabled?: boolean; // 選單是否可以用
    align?: Object; // 這個引數目前沒有被使用
    getPopupContainer?: (triggerNode: Element) => HTMLElement; // 渲染的掛載點,預設為body
    prefixCls?: string; // 樣式類的命名字首
    className?: string; // 樣式
    placement?: 'topLeft' | 'topCenter' | 'topRight' | 'bottomLeft' | 'bottomCenter' | 'bottomRight';
    // 彈出框與觸發點的對齊方式
  }複製程式碼

直接看程式碼

因為這個元件主要使用的是rc-dropdown元件
所以這裡只是對其引數做了一些封裝,比較簡單。

  export default class Dropdown extends React.Component<DropDownProps, any> {
    static Button: typeof DropdownButton;
    static defaultProps = {
      prefixCls: 'ant-dropdown',
      mouseEnterDelay: 0.15,
      mouseLeaveDelay: 0.1,
      placement: 'bottomLeft',
    };

    // 設定一個動畫效果名稱
    getTransitionName() {
      const { placement = '' } = this.props;
      // js的indexOf()可以使用在Array上也可以使用在String上
      // 使用方法一樣,第一個引數是匹配的物件,第二個引數是從哪裡開始匹配
      if (placement.indexOf('top') >= 0) {
        return 'slide-down';
      }
      return 'slide-up';
    }

    componentDidMount() {
      // 這裡就在檢測選單內容是否是antd的menu元件,並且檢測menu元件的樣式
      const { overlay } = this.props;
      const overlayProps = (overlay as any).props as any;
      // warning函式還是和之前學習的一樣的用法
      warning(
        !overlayProps.mode || overlayProps.mode === 'vertical',
        `mode="${overlayProps.mode}" is not supported for Dropdown\'s Menu.`,
      );
    }

    render() {
      const { children, prefixCls, overlay, trigger, disabled } = this.props;
      // 將dropdown包裹的觸發器加以封裝,再渲染
      const dropdownTrigger = cloneElement(children as any, {
        className: classNames((children as any).props.className, `${prefixCls}-trigger`),
        disabled,
      });
      // menu cannot be selectable in dropdown defaultly
      const overlayProps = overlay && (overlay as any).props;
      const selectable = (overlayProps && 'selectable' in overlayProps)
        ? overlayProps.selectable : false;
      // 同樣的將dropdown包裹的內容加以封裝,再渲染
      const fixedModeOverlay = cloneElement(overlay as any, {
        mode: 'vertical',
        selectable,
      });
      return (
        // 最後將所有引數傳入rc-dropdown元件
        <RcDropdown
          {...this.props}
          transitionName={this.getTransitionName()}
          trigger={disabled ? [] : trigger}
          overlay={fixedModeOverlay}
        >
          {dropdownTrigger}
        </RcDropdown>
      );
    }
  }複製程式碼

DropdownButton

這個元件是一個帶按鈕的下拉選單元件,其實其原理就是使用的之前所講的ButtonGroup來進行組合的一個元件。

想要了解ButtonGroup元件的可以點選這裡

這個元件的props繼承了兩個其他元件的props,這是typescript的interface的一個特性,可以繼承多個,來形成一個新的。

  export interface DropdownButtonProps extends ButtonGroupProps, DropDownProps {
    type?: 'primary' | 'ghost' | 'dashed'; // 按鈕型別
    disabled?: boolean; // 是否禁用
    onClick?: React.MouseEventHandler<any>; // 點選事件
    children?: any; // 子節點
  }複製程式碼

Render()

從render函式就可以看出這個元件就是antd為了方便,為大家封裝好了的一個帶下拉選單的按鈕元件

  render() {
    const {
      type, disabled, onClick, children,
      prefixCls, className, overlay, trigger, align,
      visible, onVisibleChange, placement, getPopupContainer,
      ...restProps,
    } = this.props;

    const dropdownProps = {
      align,
      overlay,
      trigger: disabled ? [] : trigger,
      onVisibleChange,
      placement,
      getPopupContainer,
    };
    if ('visible' in this.props) {
      (dropdownProps as any).visible = visible;
    }

    return (
      <ButtonGroup
        {...restProps}
        className={classNames(prefixCls, className)}
      >
        <Button
          type={type}
          disabled={disabled}
          onClick={onClick}
        >
          {children}
        </Button>
        <Dropdown {...dropdownProps}>
          <Button type={type} disabled={disabled}>
            <Icon type="down" />
          </Button>
        </Dropdown>
      </ButtonGroup>
    );
  }複製程式碼

這就完了?怎麼可能,還不過癮

寫到這裡就完了麼?是滴,這一節就完了,因為檢視了一下rc-dropdown的實現,然後根據平常看程式碼的習慣

從render()函式入口發現這一段程式碼

  render() {
    const {
      prefixCls, children,
      transitionName, animation,
      align, placement, getPopupContainer,
      showAction, hideAction,
      overlayClassName, overlayStyle,
      trigger, ...otherProps,
    } = this.props;
    return (
      <Trigger
        {...otherProps}
        prefixCls={prefixCls}
        ref={this.saveTrigger}
        popupClassName={overlayClassName}
        popupStyle={overlayStyle}
        builtinPlacements={placements}
        action={trigger}
        showAction={showAction}
        hideAction={hideAction}
        popupPlacement={placement}
        popupAlign={align}
        popupTransitionName={transitionName}
        popupAnimation={animation}
        popupVisible={this.state.visible}
        afterPopupVisibleChange={this.afterVisibleChange}
        popup={this.getMenuElement()}
        onPopupVisibleChange={this.onVisibleChange}
        getPopupContainer={getPopupContainer}
      >
        {children}
      </Trigger>
    );
  }複製程式碼

然後再看到Trigger這個元件,居然是另外一個庫rc-trigger
然後在看了rc-trigger的實現之後,才知道原來tigger元件才是下拉選單的核心。

所以還沒完呢,只是需要講解的太多,不適合在一篇文章中講全面,所以敬請期待下一篇文章,

我們將會學習rc-trigger元件的實現,之後會再寫一篇rc-dropdown的元件實現解讀,

最後看完這三篇文章,再倒過來重溫一遍,你將會學到怎麼樣一層層的包裝元件,將一個基礎元件包裝成為一個高階元件的過程。

相關文章