最近在寫B端的項目,用到了Ant Design,清爽而優雅。故想深刻源碼瞭解一二,但鑑於技術淺薄,不敢深究,故寫淺析,不喜勿噴,對其中的組件作一些分析,主要目的有兩個:javascript
本文是基於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
平時所用到的組件所有位於 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比較熟悉,對於classnames和omit.js,這裏作些說明。
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,做用就是過濾掉對象中不須要的屬性,避免把沒必要要的屬性傳遞下去
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;
接下來咱們看看 IconProps
, IconProps
是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這塊有些疑慮,能夠簡單理解爲數據類型校驗,這裏咱們可以學習到:
classNames