button JS篇ant Design of react之二

最近更新有點慢,更新慢的原因最近在看

  • 《css世界》這本書,感覺很不錯

  • 《JavaScript高級程序設計》 這本書已經看了很多遍了,主要是複習前端的基礎知識,基礎知識經常會過一段時間記憶就會慢慢模糊,特別是現在用vue、react、angularjs已經很少用原生js了,對dom的原生api方法已經忘記很多了。

  • 《夢的解析》--弗洛伊德,看這本書主要是自己的興趣愛好,裏面的內容有點難度,想通過心理學改變自己,做更好更真實的自己。

題外話說完了,這篇主要是針對上一篇對ant of react的代碼解析只是加了註釋反應很難懂,沒有那麼時間去一個一個仔細看。後面ant發佈了新版本在button組件上對動畫效果做了一些處理,大概的邏輯結構沒變。這篇就用思維導圖來展示下ant of react button組件js代碼的邏輯結構,畫的不好敬請諒解。

結構主線

按鈕的代碼邏輯結構的主線其實就是圍繞按鈕對外開放的功能實現的,所有我想來看看ant desgin of ract 按鈕組件對外開放的功能導圖:

  • disabled 按鈕失效狀態

  • ghost 幽靈屬性

  • href 點擊跳轉的地址,指定此屬性 button 的行爲和 a 鏈接一致

  • htmlType 設置 button 原生的 type 值,可選值請參考 HTML 標準

  • icon 設置按鈕的圖標類型

  • loading 設置按鈕載入狀態

  • shape 設置按鈕形狀

  • size 設置按鈕大小

  • target 相當於 a 鏈接的 target 屬性,href 存在時生效

  • type 設置按鈕類型,可選值爲 primary dashed danger(版本 2.7 中增加) 或者不設

  • onClick 點擊按鈕時的回調

  • block 將按鈕寬度調整爲其父寬度的選項

其中導致組件html結構不一樣的是href功能,所以先看href的實現

   /**
   * 組件內容
   */
  render() {
    const {
      type, shape, size, className, children, icon, prefixCls, ghost, loading: _loadingProp, block, ...rest
    } = this.props;

    const { loading, hasTwoCNChar } = this.state;

    // large => lg
    // small => sm
    let sizeCls = '';
    switch (size) {
      case 'large':
        sizeCls = 'lg';
        break;
      case 'small':
        sizeCls = 'sm';
      default:
        break;
    }

    const now = new Date();
    const isChristmas = now.getMonth() === 11 && now.getDate() === 25;
    /**
     * 拼接className
     */
    const classes = classNames(prefixCls, className, {
      [`${prefixCls}-${type}`]: type,//對應type功能
      [`${prefixCls}-${shape}`]: shape,//對應shape功能
      [`${prefixCls}-${sizeCls}`]: sizeCls,//對應size功能
      [`${prefixCls}-icon-only`]: !children && icon,//對應icon功能
      [`${prefixCls}-loading`]: loading,//對應loading功能
      [`${prefixCls}-background-ghost`]: ghost,//對應ghost功能
      [`${prefixCls}-two-chinese-chars`]: hasTwoCNChar,
      [`${prefixCls}-block`]: block,//對應block功能
      christmas: isChristmas,
    });
    /**
       * 設置圖標
       */
    const iconType = loading ? 'loading' : icon;
    const iconNode = iconType ? <Icon type={iconType} /> : null;
    const kids = (children || children === 0)
      ? React.Children.map(children, child => insertSpace(child, this.isNeedInserted())) : null;

    const title = isChristmas ? 'Ho Ho Ho!' : rest.title;
    /**
        * 判斷是a標籤還是button標籤,對應href功能
        */
    if ('href' in rest) {
      return (
        <a
          {...rest}
          className={classes}
          onClick={this.handleClick}
          title={title}
        >
          {iconNode}{kids}
        </a>
      );
    } else {
      // React does not recognize the `htmlType` prop on a DOM element. Here we pick it out of `rest`.
      const { htmlType, ...otherProps } = rest;

      return (
        <Wave>
          <button
            {...otherProps}
            type={htmlType || 'button'}
            className={classes}
            onClick={this.handleClick}
            title={title}
          >
            {iconNode}{kids}
          </button>
        </Wave>
      );
    }
  }

上面的那些功能配置屬性是通過父組件通過props傳遞進來的,那組件代碼中要有接收參數已經檢驗參數類型的處理塊:

/**
 * 類型別名,這個類型的只能是對應的值
 */
export type ButtonType = 'default' | 'primary' | 'ghost' | 'dashed' | 'danger';
export type ButtonShape = 'circle' | 'circle-outline';
export type ButtonSize = 'small' | 'default' | 'large';
export type ButtonHTMLType = 'submit' | 'button' | 'reset';
/**
 * 聲明一個接口BaseButtonProps 
 */
export interface BaseButtonProps {
  type?: ButtonType;
  icon?: string;
  shape?: ButtonShape;
  size?: ButtonSize;
  loading?: boolean | { delay?: number };
  prefixCls?: string;
  className?: string;
  ghost?: boolean;
  block?: boolean;
  children?: React.ReactNode;
}
/**
 * a標籤的參數組合
 */
export type AnchorButtonProps = {
  href: string;
  target?: string;
  onClick?: React.MouseEventHandler<HTMLAnchorElement>;
} & BaseButtonProps & React.AnchorHTMLAttributes<HTMLAnchorElement>;
/**
 * button標籤的參數組合
 */
export type NativeButtonProps = {
  htmlType?: ButtonHTMLType;
  onClick?: React.MouseEventHandler<HTMLButtonElement>;
} & BaseButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>;
/**
 * 類型別名
 */
export type ButtonProps = AnchorButtonProps | NativeButtonProps;
/**
 * button class聲明
 */
export default class Button extends React.Component<ButtonProps, any> {
  static Group: typeof Group;
  static __ANT_BUTTON = true;
  /**
   * 設置props默認值
   */
  static defaultProps = {
    prefixCls: 'ant-btn',
    loading: false,
    ghost: false,
    block: false,
  };
  /**
    * props類型校驗
    */
  static propTypes = {
    type: PropTypes.string,
    shape: PropTypes.oneOf(['circle', 'circle-outline']),
    size: PropTypes.oneOf(['large', 'default', 'small']),
    htmlType: PropTypes.oneOf(['submit', 'button', 'reset']),
    onClick: PropTypes.func,
    loading: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
    className: PropTypes.string,
    icon: PropTypes.string,
    block: PropTypes.bool,
  };

這段代碼大概意思是在typescript中聲明接口和自定義類型來校驗參數對象裏面的鍵值對的數據類型,defaultProps設置參數的某些默認值,propTypes在react中通過prop-types來校驗參數的數據量類型和值。

剩下就是單擊事件和組件聲明週期的一些處理事件

  • 組件的構造函數 聲明state的值
  /**
   * 構造函數
   */
  constructor(props: ButtonProps) {
    super(props);
    this.state = {
      loading: props.loading,
      hasTwoCNChar: false,
    };
  }
  • 單擊事件,如果是加載狀態不觸發單擊事件
  /**
    * 單擊事件
    */
  handleClick: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement> = e => {
    const { loading } = this.state;
    const { onClick } = this.props;
    if (!!loading) {
      return;
    }
    if (onClick) {
      (onClick as React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>)(e);
    }
  }
  • 組件的生命週期處理
 /**
     * 組件渲染之後調用,只調用一次。
     */
  componentDidMount() {
    this.fixTwoCNChar();
  }
  /**
    * props改變時調用觸發,nextProps.loading賦值到setState的loading
    * @param nextProps 
    */
  componentWillReceiveProps(nextProps: ButtonProps) {
    const currentLoading = this.props.loading;
    const loading = nextProps.loading;

    if (currentLoading) {
      clearTimeout(this.delayTimeout);
    }

    if (typeof loading !== 'boolean' && loading && loading.delay) {
      this.delayTimeout = window.setTimeout(() => this.setState({ loading }), loading.delay);
    } else {
      this.setState({ loading });
    }
  }
  /**
    * 組件更新完成後調用
    */
  componentDidUpdate() {
    this.fixTwoCNChar();
  }
  /**
   * 組件將要卸載時調用,清除定時器
   */
  componentWillUnmount() {
    if (this.delayTimeout) {
      clearTimeout(this.delayTimeout);
    }
  }
  /**
    * 判斷botton的內容是否有兩個中文字
    */
  fixTwoCNChar() {
    // Fix for HOC usage like <FormatMessage />
    const node = (findDOMNode(this) as HTMLElement);
    const buttonText = node.textContent || node.innerText;
    if (this.isNeedInserted() && isTwoCNChar(buttonText)) {
      if (!this.state.hasTwoCNChar) {
        this.setState({
          hasTwoCNChar: true,
        });
      }
    } else if (this.state.hasTwoCNChar) {
      this.setState({
        hasTwoCNChar: false,
      });
    }
  }
  /**
 * 判斷是否是字符串類型
 */
function isString(str: any) {
  return typeof str === 'string';
}
/**
 * 多箇中文間插入空格
 * @param {Object} child 組件的子內容
 * @param {Boolean} needInserted 是否插入空格
 * @returns {ReactElement} 
 */
// Insert one space between two chinese characters automatically.
function insertSpace(child: React.ReactChild, needInserted: boolean) {
  // Check the child if is undefined or null.
  if (child == null) {
    return;
  }
  const SPACE = needInserted ? ' ' : '';
  // strictNullChecks oops.
  if (typeof child !== 'string' && typeof child !== 'number' &&
    isString(child.type) && isTwoCNChar(child.props.children)) {
    return React.cloneElement(child, {},
      child.props.children.split('').join(SPACE));
  }
  if (typeof child === 'string') {
    if (isTwoCNChar(child)) {
      child = child.split('').join(SPACE);
    }
    return <span>{child}</span>;
  }
  return child;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章