Antd源碼淺析(一)Icon組件

前言

最近在寫B端的項目,用到了Ant Design,清爽而優雅。故想深刻源碼瞭解一二,但鑑於技術淺薄,不敢深究,故寫淺析,不喜勿噴,對其中的組件作一些分析,主要目的有兩個:javascript

  • 學習Ant Design的工程設計思路
  • 思考怎樣寫出優秀的React組件

本文是基於Ant Design3.4.4的源碼分析,讀者須要具有基本的JavaScript、React知識,對於Antd(如下用Antd表示Ant Design),螞蟻官網給出的定位是"一個服務於企業級產品的設計體系",確實,咱們的實際使用場景,大可能是寫一些後臺頁面,如CMS。當下的Antd比之React,就像Bootstrap比之jQuery,一樣Vue也不乏有Element UI之類的搭檔。css

Antd源碼是基於Typescript(系出微軟,是 JavaScript 的一個類型超集,包含它本身的編譯器,是一種類型化語言),若是有閱讀過Vue源碼的的同窗確定也會發現,Vue中使用了Flow來作一樣的事,即靜態類型檢查。JavaScript是弱類型語言,不少大型庫都加入了Flow或者Typescript,嚴謹爲之。html

目錄結構

打開Antd源碼目錄,結構仍是比較簡潔:java

![alt text](/images/antd/1.png)

平時所用到的組件所有位於 components 文件夾下,首先咱們分析一個簡單的組件Icon,打開 components/icon ,目錄結構以下:react

圖片描述

這裏不得不說Antd的文檔是很友好的,目錄內的以 .md 結尾的文件給出了中英文的使用說明,也就是咱們在在其官網看到的說明文檔。git

代碼

Icon的核心代碼位於 index.tsx 內,這裏說明一下,對於不熟悉Typescript的同窗來講這個文件類型可能有些陌生,Typescript主要是豐富了JavaScript的內容和加入了靜態類型檢查,通常的Typescript文件是以 .ts 結尾,但相對於React的jsx文件,Typescript產生了 .tsx 的文件,其實就是Typescript的jsx寫法,實際生產環境中,最終都要編譯成 .js 文件。github

如下是Icon組件中 index.tsx 的所有源碼:編程

import * as React from 'react';
import classNames from 'classnames';
import omit from 'omit.js';

export interface IconProps {
  type: string;
  className?: string;
  title?: string;
  onClick?: React.MouseEventHandler<any>;
  spin?: boolean;
  style?: React.CSSProperties;
}

const Icon = (props: IconProps) => {
  const { type, className = '', spin } = props;
  const classString = classNames({
    anticon: true,
    'anticon-spin': !!spin || type === 'loading',
    [`anticon-${type}`]: true,
  }, className);
  return <i {...omit(props, ['type', 'spin'])} className={classString} />;
};

export default Icon;

咱們看看官網使用示例和API描述:數組

<Icon type="question" style={{ fontSize: 16, color: '#08c' }} />
參數 說明 類型 默認值
spin 是否有旋轉動畫 boolean false
style 設置圖標的樣式,例如 fontSize 和 color object -
type 圖標類型 string -

首先導入的是3個依賴antd

import * as React from 'react';
import classNames from 'classnames';
import omit from 'omit.js';

你們對React比較熟悉,對於classnamesomit.js,這裏作些說明。

classNames基本使用方法

classnames主要是爲組件提供動態css功能,方便向React之類的應用提供狀態編程

var classNames = require('classnames');
classNames('foo', 'bar'); // => 'foo bar'

classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'

// 不一樣的參數類型
classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'

// 忽略錯誤的數據類型
classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'

// 數組參數
var arr = ['b', { c: true, d: false }];
classNames('a', arr); // => 'a b c'

classnames可以很簡便的處理css的class開關,相似於在jsx中

{ true? 'class-a': 'class-b' }

可是要優雅和方便不少,結合ES2015中的字符串變量,就能夠玩的更開心

let buttonType = 'primary';
classNames({ [`btn-${buttonType}`]: true });

固然怎麼能少了直接使用React

var classNames = require('classnames');

var Button = React.createClass({
  // ...
  render () {
    var btnClass = classNames({
      btn: true,
      'btn-pressed': this.state.isPressed,
      'btn-over': !this.state.isPressed && this.state.isHovered
    });
    return <button className={btnClass}>{this.props.label}</button>;
  }
});

omit.js基本使用方法

omit.js,做用就是過濾掉對象中不須要的屬性,避免把沒必要要的屬性傳遞下去

var omit = require('omit.js');
omit({ name: 'Benjy', age: 18 }, [ 'name' ]); // => { age: 18 }

這個庫的源碼很簡單,直接貼出:

function omit(obj, fields) {
  const shallowCopy = {
    ...obj,
  };
  for (let i = 0; i < fields.length; i++) {
    const key = fields[i];
    delete shallowCopy[key];
  }
  return shallowCopy;
}

export default omit;

屬性校驗

接下來咱們看看 IconPropsIconProps 是Icon組件的參數驗證器,做用和React中的 PropTypes 相同,確保你接收到的數據是有效的,可以在識別些某些類型問題,因此React官方也建議,對於更大的代碼庫使用Flow或者TypeScript來替代 PropTypes ,Antd的開發使用了TypeScript。

export interface IconProps {
  type: string;    // 圖標類型必須爲string
  className?: string;   // className類型必須爲string
  title?: string;   // title類型必須爲string
  onClick?: React.MouseEventHandler<any>;    // onClick類型必須爲React.MouseEventHandler
  spin?: boolean;    // 是否有旋轉動畫類型必須爲boolean
  style?: React.CSSProperties;  // style類型必須爲React.CSSProperties
}

在這裏 ? 表明參數可選,對於 React.MouseEventHandler<any>React.CSSProperties 是TypeScript爲React定義的數據類型, <> 爲泛型標識,咱們不妨以 React.MouseEventHandler<any> 爲例子深刻看一下TypeScript實現的事件類型定義,若是不理解,能夠簡單理解爲一種數據類型。

// 第一層
React.MouseEventHandler<any>  
  
// 第二層
type MouseEventHandler<T> = EventHandler<MouseEvent<T>>;    

// 第三層
interface MouseEvent<T> extends SyntheticEvent<T> {    
    altKey: boolean;
    button: number;
    buttons: number;
    clientX: number;
    clientY: number;
    ctrlKey: boolean;
    /**
     * See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method.
     */
    getModifierState(key: string): boolean;
    metaKey: boolean;
    nativeEvent: NativeMouseEvent;
    pageX: number;
    pageY: number;
    relatedTarget: EventTarget;
    screenX: number;
    screenY: number;
    shiftKey: boolean;
}    


// 第四層
// Event System
// ----------------------------------------------------------------------

interface SyntheticEvent<T> {
    bubbles: boolean;
    /**
     * A reference to the element on which the event listener is registered.
     */
    currentTarget: EventTarget & T;
    cancelable: boolean;
    defaultPrevented: boolean;
    eventPhase: number;
    isTrusted: boolean;
    nativeEvent: Event;
    preventDefault(): void;
    isDefaultPrevented(): boolean;
    stopPropagation(): void;
    isPropagationStopped(): boolean;
    persist(): void;
    // If you thought this should be `EventTarget & T`, see https://github.com/DefinitelyTyped/DefinitelyTyped/pull/12239
    /**
     * A reference to the element from which the event was originally dispatched.
     * This might be a child element to the element on which the event listener is registered.
     *
     * @see currentTarget
     */
    target: EventTarget;
    timeStamp: number;
    type: string;
}

以上是React類型定義的源碼,小夥伴們是否是可以理解一些了,若是咱們用React自己實現Icon的驗證,有以下寫法:

import PropTypes from 'prop-types';

Icon.propTypes = {
  type: PropTypes.string;
  className: PropTypes.string;
  title: PropTypes.string;
  onClick: PropTypes.func;
  spin: PropTypes.bool;
  style: PropTypes.object;
};

主體代碼

const Icon = (props: IconProps) => {
  const { type, className = '', spin } = props;
  const classString = classNames({
    anticon: true,
    'anticon-spin': !!spin || type === 'loading',
    [`anticon-${type}`]: true,
  }, className);
  return <i {...omit(props, ['type', 'spin'])} className={classString} />;
};

能夠看到Antd使用 <i> 標籤來實現Icon組件,首先經過 IconProps 校驗參數,而後組合 className ,默認添加 anticon ,判斷 spin 屬性,選擇是否添加 anticon-spin ,接着添加 anticon-${type}屬性,生成 className ,經過 omit 過濾掉 type , spin 屬性,由於這倆屬性對於 <i> 標籤是沒有意義的,爲了理解咱們舉個實際使用例子。

<Icon type="question" style={{ fontSize: 16 }} />

生成的HTML中的代碼以下:

<i class="anticon anticon-question" style="font-size: 16px;"></i>

總結

到這裏對於Icon組件,咱們就能直觀的看到其實現原理了,可能部分讀者對於TypeScript這塊有些疑慮,能夠簡單理解爲數據類型校驗,這裏咱們可以學習到:

  • Antd組件實現的基本結構和思路
  • 組件對於參數的校驗的方式
  • 優雅的處理 classNames
  • 省略一些沒必要要的參數
相關文章
相關標籤/搜索